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 
z_shell_op_cursor_vert_move(const struct shell * shell,int32_t delta)10 void z_shell_op_cursor_vert_move(const struct shell *shell, int32_t delta)
11 {
12 	if (delta != 0) {
13 		z_shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c",
14 				    delta > 0 ? delta : -delta,
15 				    delta > 0 ? 'A' : 'B');
16 	}
17 }
18 
z_shell_op_cursor_horiz_move(const struct shell * shell,int32_t delta)19 void z_shell_op_cursor_horiz_move(const struct shell *shell, int32_t delta)
20 {
21 	if (delta != 0) {
22 		z_shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c",
23 				    delta > 0 ? delta : -delta,
24 				    delta > 0 ? 'C' : 'D');
25 	}
26 }
27 
28 /* Function returns true if command length is equal to multiplicity of terminal
29  * width.
30  */
full_line_cmd(const struct shell * shell)31 static inline bool full_line_cmd(const struct shell *shell)
32 {
33 	return ((shell->ctx->cmd_buff_len + z_shell_strlen(shell->ctx->prompt))
34 			% shell->ctx->vt100_ctx.cons.terminal_wid == 0U);
35 }
36 
37 /* Function returns true if cursor is at beginning of an empty line. */
z_shell_cursor_in_empty_line(const struct shell * shell)38 bool z_shell_cursor_in_empty_line(const struct shell *shell)
39 {
40 	return ((shell->ctx->cmd_buff_pos + z_shell_strlen(shell->ctx->prompt))
41 			% shell->ctx->vt100_ctx.cons.terminal_wid == 0U);
42 }
43 
z_shell_op_cond_next_line(const struct shell * shell)44 void z_shell_op_cond_next_line(const struct shell *shell)
45 {
46 	if (z_shell_cursor_in_empty_line(shell) || full_line_cmd(shell)) {
47 		z_cursor_next_line_move(shell);
48 	}
49 }
50 
z_shell_op_cursor_position_synchronize(const struct shell * shell)51 void z_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 	z_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 		z_cursor_next_line_move(shell);
65 	}
66 
67 	if (last_line) {
68 		z_shell_op_cursor_horiz_move(shell, cons->cur_x -
69 							       cons->cur_x_end);
70 	} else {
71 		z_shell_op_cursor_vert_move(shell, cons->cur_y_end - cons->cur_y);
72 		z_shell_op_cursor_horiz_move(shell, cons->cur_x -
73 							       cons->cur_x_end);
74 	}
75 }
76 
z_shell_op_cursor_move(const struct shell * shell,int16_t val)77 void z_shell_op_cursor_move(const struct shell *shell, int16_t val)
78 {
79 	struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons;
80 	uint16_t new_pos = shell->ctx->cmd_buff_pos + val;
81 	int32_t row_span;
82 	int32_t col_span;
83 
84 	z_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 = z_row_span_with_buffer_offsets_get(
89 						&shell->ctx->vt100_ctx.cons,
90 						shell->ctx->cmd_buff_pos,
91 						new_pos);
92 	col_span = z_column_span_with_buffer_offsets_get(
93 						&shell->ctx->vt100_ctx.cons,
94 						shell->ctx->cmd_buff_pos,
95 						new_pos);
96 
97 	z_shell_op_cursor_vert_move(shell, -row_span);
98 	z_shell_op_cursor_horiz_move(shell, col_span);
99 	shell->ctx->cmd_buff_pos = new_pos;
100 }
101 
shift_calc(const char * str,uint16_t pos,uint16_t len,int16_t sign)102 static uint16_t shift_calc(const char *str, uint16_t pos, uint16_t len, int16_t sign)
103 {
104 	bool found = false;
105 	uint16_t ret = 0U;
106 	uint16_t idx;
107 
108 	while (1) {
109 		idx = pos + ret * sign;
110 		if (((idx == 0U) && (sign < 0)) ||
111 		    ((idx == len) && (sign > 0))) {
112 			break;
113 		}
114 		if (isalnum((int)str[idx]) != 0) {
115 			found = true;
116 		} else {
117 			if (found) {
118 				break;
119 			}
120 		}
121 		ret++;
122 	}
123 
124 	return ret;
125 }
126 
z_shell_op_cursor_word_move(const struct shell * shell,int16_t val)127 void z_shell_op_cursor_word_move(const struct shell *shell, int16_t val)
128 {
129 	int16_t shift;
130 	int16_t sign;
131 
132 	if (val < 0) {
133 		val = -val;
134 		sign = -1;
135 	} else {
136 		sign = 1;
137 	}
138 
139 	while (val--) {
140 		shift = shift_calc(shell->ctx->cmd_buff,
141 				   shell->ctx->cmd_buff_pos,
142 				   shell->ctx->cmd_buff_len, sign);
143 		z_shell_op_cursor_move(shell, sign * shift);
144 	}
145 }
146 
z_shell_op_word_remove(const struct shell * shell)147 void z_shell_op_word_remove(const struct shell *shell)
148 {
149 	char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos - 1];
150 	char *str_start = &shell->ctx->cmd_buff[0];
151 	uint16_t chars_to_delete;
152 
153 	/* Line must not be empty and cursor must not be at 0 to continue. */
154 	if ((shell->ctx->cmd_buff_len == 0) ||
155 	    (shell->ctx->cmd_buff_pos == 0)) {
156 		return;
157 	}
158 
159 	/* Start at the current position. */
160 	chars_to_delete = 0U;
161 
162 	/* Look back for all spaces then for non-spaces. */
163 	while ((str >= str_start) && (*str == ' ')) {
164 		++chars_to_delete;
165 		--str;
166 	}
167 
168 	while ((str >= str_start) && (*str != ' ')) {
169 		++chars_to_delete;
170 		--str;
171 	}
172 
173 	/* Manage the buffer. */
174 	memmove(str + 1, str + 1 + chars_to_delete,
175 		shell->ctx->cmd_buff_len - chars_to_delete);
176 	shell->ctx->cmd_buff_len -= chars_to_delete;
177 	shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0';
178 
179 	/* Update display. */
180 	z_shell_op_cursor_move(shell, -chars_to_delete);
181 	z_cursor_save(shell);
182 	z_shell_fprintf(shell, SHELL_NORMAL, "%s", str + 1);
183 	z_clear_eos(shell);
184 	z_cursor_restore(shell);
185 }
186 
z_shell_op_cursor_home_move(const struct shell * shell)187 void z_shell_op_cursor_home_move(const struct shell *shell)
188 {
189 	z_shell_op_cursor_move(shell, -shell->ctx->cmd_buff_pos);
190 }
191 
z_shell_op_cursor_end_move(const struct shell * shell)192 void z_shell_op_cursor_end_move(const struct shell *shell)
193 {
194 	z_shell_op_cursor_move(shell, shell->ctx->cmd_buff_len -
195 						shell->ctx->cmd_buff_pos);
196 }
197 
z_shell_op_left_arrow(const struct shell * shell)198 void z_shell_op_left_arrow(const struct shell *shell)
199 {
200 	if (shell->ctx->cmd_buff_pos > 0) {
201 		z_shell_op_cursor_move(shell, -1);
202 	}
203 }
204 
z_shell_op_right_arrow(const struct shell * shell)205 void z_shell_op_right_arrow(const struct shell *shell)
206 {
207 	if (shell->ctx->cmd_buff_pos < shell->ctx->cmd_buff_len) {
208 		z_shell_op_cursor_move(shell, 1);
209 	}
210 }
211 
reprint_from_cursor(const struct shell * shell,uint16_t diff,bool data_removed)212 static void reprint_from_cursor(const struct shell *shell, uint16_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 		z_clear_eos(shell);
224 	}
225 
226 	if (z_flag_obscure_get(shell)) {
227 		int len = strlen(&shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]);
228 
229 		while (len--) {
230 			z_shell_raw_fprintf(shell->fprintf_ctx, "*");
231 		}
232 	} else {
233 		z_shell_fprintf(shell, SHELL_NORMAL, "%s",
234 			      &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]);
235 	}
236 	shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len;
237 
238 	if (full_line_cmd(shell)) {
239 		if (((data_removed) && (diff > 0)) || (!data_removed)) {
240 			z_cursor_next_line_move(shell);
241 		}
242 	}
243 
244 	z_shell_op_cursor_move(shell, -diff);
245 }
246 
data_insert(const struct shell * shell,const char * data,uint16_t len)247 static void data_insert(const struct shell *shell, const char *data, uint16_t len)
248 {
249 	uint16_t after = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos;
250 	char *curr_pos = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos];
251 
252 	if ((shell->ctx->cmd_buff_len + len) >= CONFIG_SHELL_CMD_BUFF_SIZE) {
253 		return;
254 	}
255 
256 	memmove(curr_pos + len, curr_pos, after);
257 	memcpy(curr_pos, data, len);
258 	shell->ctx->cmd_buff_len += len;
259 	shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0';
260 
261 	if (!z_flag_echo_get(shell)) {
262 		shell->ctx->cmd_buff_pos += len;
263 		return;
264 	}
265 
266 	reprint_from_cursor(shell, after, false);
267 }
268 
char_replace(const struct shell * shell,char data)269 static void char_replace(const struct shell *shell, char data)
270 {
271 	shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos++] = data;
272 
273 	if (!z_flag_echo_get(shell)) {
274 		return;
275 	}
276 	if (z_flag_obscure_get(shell)) {
277 		data = '*';
278 	}
279 
280 	z_shell_raw_fprintf(shell->fprintf_ctx, "%c", data);
281 	if (z_shell_cursor_in_empty_line(shell)) {
282 		z_cursor_next_line_move(shell);
283 	}
284 }
285 
z_shell_op_char_insert(const struct shell * shell,char data)286 void z_shell_op_char_insert(const struct shell *shell, char data)
287 {
288 	if (shell->ctx->internal.flags.insert_mode &&
289 		(shell->ctx->cmd_buff_len != shell->ctx->cmd_buff_pos)) {
290 		char_replace(shell, data);
291 	} else {
292 		data_insert(shell, &data, 1);
293 	}
294 }
295 
z_shell_op_char_backspace(const struct shell * shell)296 void z_shell_op_char_backspace(const struct shell *shell)
297 {
298 	if ((shell->ctx->cmd_buff_len == 0) ||
299 	    (shell->ctx->cmd_buff_pos == 0)) {
300 		return;
301 	}
302 
303 	z_shell_op_cursor_move(shell, -1);
304 	z_shell_op_char_delete(shell);
305 }
306 
z_shell_op_char_delete(const struct shell * shell)307 void z_shell_op_char_delete(const struct shell *shell)
308 {
309 	uint16_t diff = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos;
310 	char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos];
311 
312 	if (diff == 0U) {
313 		return;
314 	}
315 
316 	memmove(str, str + 1, diff);
317 	--shell->ctx->cmd_buff_len;
318 	reprint_from_cursor(shell, --diff, true);
319 }
320 
z_shell_op_delete_from_cursor(const struct shell * shell)321 void z_shell_op_delete_from_cursor(const struct shell *shell)
322 {
323 	shell->ctx->cmd_buff_len = shell->ctx->cmd_buff_pos;
324 	shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos] = '\0';
325 
326 	z_clear_eos(shell);
327 }
328 
z_shell_op_completion_insert(const struct shell * shell,const char * compl,uint16_t compl_len)329 void z_shell_op_completion_insert(const struct shell *shell,
330 				  const char *compl,
331 				  uint16_t compl_len)
332 {
333 	data_insert(shell, compl, compl_len);
334 }
335 
z_shell_cmd_line_erase(const struct shell * shell)336 void z_shell_cmd_line_erase(const struct shell *shell)
337 {
338 	z_shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons,
339 				    shell->ctx->cmd_buff_pos,
340 				    shell->ctx->cmd_buff_len);
341 	z_shell_op_cursor_horiz_move(shell,
342 				   -(shell->ctx->vt100_ctx.cons.cur_x - 1));
343 	z_shell_op_cursor_vert_move(shell, shell->ctx->vt100_ctx.cons.cur_y - 1);
344 
345 	z_clear_eos(shell);
346 }
347 
print_prompt(const struct shell * shell)348 static void print_prompt(const struct shell *shell)
349 {
350 	z_shell_fprintf(shell, SHELL_INFO, "%s", shell->ctx->prompt);
351 }
352 
z_shell_print_cmd(const struct shell * shell)353 void z_shell_print_cmd(const struct shell *shell)
354 {
355 	z_shell_raw_fprintf(shell->fprintf_ctx, "%s", shell->ctx->cmd_buff);
356 }
357 
z_shell_print_prompt_and_cmd(const struct shell * shell)358 void z_shell_print_prompt_and_cmd(const struct shell *shell)
359 {
360 	print_prompt(shell);
361 
362 	if (z_flag_echo_get(shell)) {
363 		z_shell_print_cmd(shell);
364 		z_shell_op_cursor_position_synchronize(shell);
365 	}
366 }
367 
shell_pend_on_txdone(const struct shell * shell)368 static void shell_pend_on_txdone(const struct shell *shell)
369 {
370 	if (IS_ENABLED(CONFIG_MULTITHREADING) &&
371 	    (shell->ctx->state < SHELL_STATE_PANIC_MODE_ACTIVE)) {
372 		struct k_poll_event event;
373 
374 		k_poll_event_init(&event,
375 				  K_POLL_TYPE_SIGNAL,
376 				  K_POLL_MODE_NOTIFY_ONLY,
377 				  &shell->ctx->signals[SHELL_SIGNAL_TXDONE]);
378 		k_poll(&event, 1, K_FOREVER);
379 		k_poll_signal_reset(&shell->ctx->signals[SHELL_SIGNAL_TXDONE]);
380 	} else {
381 		/* Blocking wait in case of bare metal. */
382 		while (!z_flag_tx_rdy_get(shell)) {
383 		}
384 		z_flag_tx_rdy_set(shell, false);
385 	}
386 }
387 
z_shell_write(const struct shell * shell,const void * data,size_t length)388 void z_shell_write(const struct shell *shell, const void *data,
389 		 size_t length)
390 {
391 	__ASSERT_NO_MSG(shell && data);
392 
393 	size_t offset = 0;
394 	size_t tmp_cnt;
395 
396 	while (length) {
397 		int err = shell->iface->api->write(shell->iface,
398 				&((const uint8_t *) data)[offset], length,
399 				&tmp_cnt);
400 		(void)err;
401 		__ASSERT_NO_MSG(err == 0);
402 		__ASSERT_NO_MSG(length >= tmp_cnt);
403 		offset += tmp_cnt;
404 		length -= tmp_cnt;
405 		if (tmp_cnt == 0 &&
406 		    (shell->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) {
407 			shell_pend_on_txdone(shell);
408 		}
409 	}
410 }
411 
412 /* Function shall be only used by the fprintf module. */
z_shell_print_stream(const void * user_ctx,const char * data,size_t len)413 void z_shell_print_stream(const void *user_ctx, const char *data, size_t len)
414 {
415 	z_shell_write((const struct shell *) user_ctx, data, len);
416 }
417 
vt100_bgcolor_set(const struct shell * shell,enum shell_vt100_color bgcolor)418 static void vt100_bgcolor_set(const struct shell *shell,
419 			      enum shell_vt100_color bgcolor)
420 {
421 	if ((bgcolor == SHELL_NORMAL) ||
422 	    (shell->ctx->vt100_ctx.col.bgcol == bgcolor)) {
423 		return;
424 	}
425 
426 	/* -1 because default value is first in enum */
427 	uint8_t cmd[] = SHELL_VT100_BGCOLOR(bgcolor - 1);
428 
429 	shell->ctx->vt100_ctx.col.bgcol = bgcolor;
430 	z_shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
431 
432 }
433 
z_shell_vt100_color_set(const struct shell * shell,enum shell_vt100_color color)434 void z_shell_vt100_color_set(const struct shell *shell,
435 			     enum shell_vt100_color color)
436 {
437 
438 	if (shell->ctx->vt100_ctx.col.col == color) {
439 		return;
440 	}
441 
442 	shell->ctx->vt100_ctx.col.col = color;
443 
444 	if (color != SHELL_NORMAL) {
445 
446 		uint8_t cmd[] = SHELL_VT100_COLOR(color - 1);
447 
448 		z_shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
449 	} else {
450 		static const uint8_t cmd[] = SHELL_VT100_MODESOFF;
451 
452 		z_shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd);
453 	}
454 }
455 
z_shell_vt100_colors_restore(const struct shell * shell,const struct shell_vt100_colors * color)456 void z_shell_vt100_colors_restore(const struct shell *shell,
457 				       const struct shell_vt100_colors *color)
458 {
459 	z_shell_vt100_color_set(shell, color->col);
460 	vt100_bgcolor_set(shell, color->bgcol);
461 }
462 
z_shell_vfprintf(const struct shell * shell,enum shell_vt100_color color,const char * fmt,va_list args)463 void z_shell_vfprintf(const struct shell *shell, enum shell_vt100_color color,
464 		      const char *fmt, va_list args)
465 {
466 	if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) &&
467 	    shell->ctx->internal.flags.use_colors &&
468 	    (color != shell->ctx->vt100_ctx.col.col)) {
469 		struct shell_vt100_colors col;
470 
471 		z_shell_vt100_colors_store(shell, &col);
472 		z_shell_vt100_color_set(shell, color);
473 
474 		z_shell_fprintf_fmt(shell->fprintf_ctx, fmt, args);
475 
476 		z_shell_vt100_colors_restore(shell, &col);
477 	} else {
478 		z_shell_fprintf_fmt(shell->fprintf_ctx, fmt, args);
479 	}
480 }
481 
z_shell_fprintf(const struct shell * sh,enum shell_vt100_color color,const char * fmt,...)482 void z_shell_fprintf(const struct shell *sh,
483 		     enum shell_vt100_color color,
484 		     const char *fmt, ...)
485 {
486 	__ASSERT_NO_MSG(sh);
487 	__ASSERT_NO_MSG(sh->ctx);
488 	__ASSERT_NO_MSG(sh->fprintf_ctx);
489 	__ASSERT_NO_MSG(fmt);
490 	__ASSERT(z_flag_panic_mode_get(sh) || !k_is_in_isr(),
491 		 "Thread context required.");
492 
493 	va_list args;
494 
495 	va_start(args, fmt);
496 	z_shell_vfprintf(sh, color, fmt, args);
497 	va_end(args);
498 }
499