blob: e2a6e224192f4a64239f790ecc3f5f4313e206aa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <inttypes.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <bsp/bsp.h>
#include "os/mynewt.h"
#include "os/os_task.h"
#include "console/console.h"
#include "console/ticks.h"
#include "console_priv.h"
#include "console/history.h"
/* Control characters */
#define ESC 0x1b
#define DEL 0x7f
#define BS 0x08
#define ETX 0x03
#define EOT 0x04
#define FF 0x0B
#define VT 0x0C
#define CSI "\x1b["
/* ANSI escape sequences */
#define ANSI_ESC '['
#define ANSI_UP 'A'
#define ANSI_DOWN 'B'
#define ANSI_FORWARD 'C'
#define ANSI_BACKWARD 'D'
#define ANSI_END 'F'
#define ANSI_HOME 'H'
#define ANSI_DEL '~'
#define DSR_CPS 'R'
#define ESC_ESC (1 << 0)
#define ESC_ANSI (1 << 1)
#define ESC_ANSI_FIRST (1 << 2)
#define ESC_ANSI_VAL (1 << 3)
#define ESC_ANSI_VAL_2 (1 << 4)
#define CONSOLE_NLIP_PKT_START1 (6)
#define CONSOLE_NLIP_PKT_START2 (9)
#define CONSOLE_NLIP_DATA_START1 (4)
#define CONSOLE_NLIP_DATA_START2 (20)
#define NLIP_PKT_START1 (CONSOLE_NLIP_PKT_START1)
#define NLIP_PKT_START2 (CONSOLE_NLIP_PKT_START2)
#define NLIP_DATA_START1 (CONSOLE_NLIP_DATA_START1)
#define NLIP_DATA_START2 (CONSOLE_NLIP_DATA_START2)
/* Indicates whether the previous line of output was completed. */
int console_is_midline;
uint8_t g_is_output_nlip;
#if MYNEWT_VAL(CONSOLE_COMPAT)
#define CONSOLE_COMPAT_MAX_CMD_QUEUED 1
static struct console_input buf[CONSOLE_COMPAT_MAX_CMD_QUEUED];
static struct os_event shell_console_ev[CONSOLE_COMPAT_MAX_CMD_QUEUED];
static console_rx_cb console_compat_rx_cb; /* callback that input is ready */
static struct os_eventq compat_avail_queue;
static struct os_eventq compat_lines_queue;
#endif
static int esc_state;
static int nlip_state;
static int echo = MYNEWT_VAL(CONSOLE_ECHO);
static unsigned int ansi_val, ansi_val_2;
static bool rx_stalled;
/* Cursor position in input line */
static uint16_t cur;
/* Number of characters after cursor in input line */
static uint16_t trailing_chars;
static struct os_eventq avail_queue;
static struct os_eventq *lines_queue;
static struct os_event *current_line_ev;
static completion_cb completion;
bool g_console_silence;
bool g_console_silence_non_nlip;
bool g_console_ignore_non_nlip;
static struct os_mutex console_write_lock;
/* Last character to write to console was LF but it was not printed yet */
static bool holding_lf;
static bool prompt_has_focus;
static bool terminal_initialized;
static bool terminal_size_requested;
/* Maximum row as reported by terminal, 0 - terminal size is not known yet */
static int max_row;
/* Buffer for prompt */
static char console_prompt[MYNEWT_VAL(CONSOLE_PROMPT_MAX_LEN)];
/* Length of prompt stored in console_prompt */
static uint16_t prompt_len;
/* Current history line, 0 no history line */
static history_handle_t history_line;
/*
* Default implementation in case all consoles are disabled - we just ignore any
* output to console.
*/
int __attribute__((weak))
console_out_nolock(int c)
{
return c;
}
void
console_echo(int on)
{
echo = on;
}
int
console_lock(int timeout)
{
int rc = OS_OK;
/* Locking from isr while some task own mutex fails with OS_EBUSY */
if (os_arch_in_isr()) {
if (os_mutex_get_level(&console_write_lock)) {
rc = OS_EBUSY;
}
goto end;
}
rc = os_mutex_pend(&console_write_lock, timeout);
if (rc == OS_NOT_STARTED) {
/* No need to block before system start make it OK */
rc = OS_OK;
}
end:
return rc;
}
int
console_unlock(void)
{
int rc = OS_OK;
if (os_arch_in_isr()) {
goto end;
}
rc = os_mutex_release(&console_write_lock);
assert(rc == OS_OK || rc == OS_NOT_STARTED);
end:
return rc;
}
/*
* Helper function to write sequence of characters to terminal without
* checking for new lines or anything else.
*/
static void
console_write_nolock(const char *str, int cnt)
{
int i;
for (i = 0; i < cnt; i++) {
if (console_out_nolock((int)str[i]) == EOF) {
break;
}
}
}
/*
* Helper function to write null terminated strings.
*/
static void
console_write_str(const char *str)
{
console_write_nolock(str, strlen(str));
}
static int
console_filter_out(int c)
{
if (g_console_silence) {
return c;
}
/* For nlip output or when prompt has console write character */
if (prompt_has_focus || g_is_output_nlip) {
return console_out_nolock(c);
}
/*
* For logs output don't write last newt line character at once.
* Writing new would scroll window and blank line would be
* visible as last line on terminal.
* When terminal size is known, last line is used for prompt
* and command line editing.
* Following code prevents empty line to separate last log
* line from prompt editor.
*/
console_is_midline = c != '\n' && c != '\r';
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) && max_row > 0) {
if (c == '\n') {
console_is_midline = false;
if (holding_lf) {
console_out_nolock(c);
} else {
holding_lf = true;
}
} else {
if (holding_lf) {
console_out_nolock('\n');
holding_lf = false;
}
console_is_midline = c != '\r';
c = console_out_nolock(c);
}
} else {
c = console_out_nolock(c);
}
return c;
}
/*
* Helper function to write sequence of characters to console with new
* line checking.
*/
static void
console_filter_write(const char *str, int cnt)
{
int i;
for (i = 0; i < cnt; i++) {
if (console_filter_out((int)str[i]) == EOF) {
break;
}
}
}
/*
* Helper function for terminal escape sequences with number parameter.
* It adds ascii encoded number plus one character.
*/
static char *
add_ascii_num_with_char(char *str, unsigned int num, char c)
{
char *p = str;
char *s;
char tmp;
/* Put digits in reverse order first. */
do {
*p++ = (num % 10) + '0';
num /= 10;
} while (num);
s = str;
str = p;
--p;
/* Revers order of characters, to get correct number representation */
while (s < p) {
tmp = *s;
*s++ = *p;
*p-- = tmp;
}
*str++ = c;
*str = '\0';
return str;
}
/*
* Sends terminal escape sequence to set cursor at given row and column.
* CSI <row> ; <column> H
*/
static void
console_cursor_set(int row, int column)
{
char seq[24] = CSI;
char *p;
p = add_ascii_num_with_char(seq + 2, row, ';');
p = add_ascii_num_with_char(p, column, 'H');
console_write_nolock(seq, p - seq);
}
static inline void
cursor_save(void)
{
console_out_nolock(ESC);
console_out_nolock('[');
console_out_nolock('s');
}
static inline void
cursor_restore(void)
{
console_out_nolock(ESC);
console_out_nolock('[');
console_out_nolock('u');
}
static inline void
cursor_clear_line(void)
{
console_out_nolock(ESC);
console_out_nolock('[');
console_out_nolock('K');
}
/*
* Function sends sequence to get terminal size.
* It does it by setting cursor position to some big numbers and
* then requesting cursor position which should be adjusted by
* terminal to actual terminal windows size.
*/
static void
request_terminal_size(void)
{
/* If terminal size was requested, forget any previous value. */
max_row = 0;
cursor_save();
console_write_str(CSI "1;999r" CSI "999;999H" CSI "6n");
cursor_restore();
terminal_size_requested = true;
}
static void
console_init_terminal(void)
{
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) && !terminal_initialized) {
console_write_str(CSI "!p"
CSI "1;999r"
CSI "999;1H\n\n"
CSI "A"
CSI "s");
console_is_midline = 0;
terminal_initialized = true;
max_row = 0;
}
}
/*
* Switch output to prompt portion of the terminal (last line).
*/
static void
console_switch_to_prompt(void)
{
struct console_input *input;
char c;
console_init_terminal();
/* If terminal size is not known yet, try asking terminal first */
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) &&
max_row == 0 && !terminal_size_requested) {
request_terminal_size();
}
/*
* If terminal size is known and cursor is currently on log portion
* of the screen, save cursor position and set cursor at place
* that is in sync with 'cur' variable.
*/
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) &&
!prompt_has_focus && max_row) {
cursor_save();
prompt_has_focus = true;
console_cursor_set(max_row, prompt_len + cur + 1);
if (MYNEWT_VAL(CONSOLE_PROMPT_SOFT_CURSOR)) {
if (trailing_chars && current_line_ev && current_line_ev->ev_arg) {
input = (struct console_input *)current_line_ev->ev_arg;
c = input->line[cur];
} else {
c = ' ';
}
console_write_str(CSI "0m");
console_out_nolock(c);
console_out_nolock('\b');
if (MYNEWT_VAL(CONSOLE_PROMPT_HIDE_CURSOR_IN_LOG_AREA)) {
console_write_str(CSI "?25h");
}
}
}
}
/*
* Switch to log portion of the terminal.
* Cursor will be restored somewhere above bottom line.
*/
static void
console_switch_to_logs(void)
{
struct console_input *input;
char c;
if (g_is_output_nlip) {
return;
}
console_init_terminal();
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) && prompt_has_focus) {
if (MYNEWT_VAL(CONSOLE_PROMPT_SOFT_CURSOR)) {
console_write_str(CSI);
console_write_str(MYNEWT_VAL(CONSOLE_PROMPT_SOFT_CURSOR_ATTR));
if (trailing_chars && current_line_ev && current_line_ev->ev_arg) {
input = (struct console_input *)current_line_ev->ev_arg;
c = input->line[cur];
} else {
c = ' ';
}
console_out_nolock(c);
if (MYNEWT_VAL(CONSOLE_PROMPT_HIDE_CURSOR_IN_LOG_AREA)) {
console_write_str(CSI "?25l");
}
console_write_str(CSI "0m\b");
}
cursor_restore();
prompt_has_focus = false;
}
}
/*
* Set terminal scrolling region.
* CSI <top> ; <bottom> r
*/
static void
console_set_scrolling_region(int top, int bottom)
{
char seq[24] = CSI;
char *p;
p = add_ascii_num_with_char(seq + 2, top, ';');
p = add_ascii_num_with_char(p, bottom, 'r');
console_write_nolock(seq, p - seq);
}
void
console_prompt_set(const char *prompt, const char *line)
{
bool locked;
prompt_len = strlen(prompt);
/* If this assert fails increase value of CONSOLE_PROMPT_MAX_LEN */
assert(MYNEWT_VAL(CONSOLE_PROMPT_MAX_LEN) > prompt_len);
strcpy(console_prompt, prompt);
/* Place cursor at the end, no trailing chars */
if (line) {
cur = strlen(line);
} else {
cur = 0;
}
trailing_chars = 0;
locked = console_lock(1000) == OS_OK;
console_switch_to_prompt();
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) && prompt_has_focus) {
console_write_str(CSI "999;1H");
console_write_nolock(prompt, prompt_len);
console_write_nolock(line, cur);
console_write_str(CSI "K");
} else {
console_write(prompt, prompt_len);
console_write(line, cur);
}
if (locked) {
(void)console_unlock();
}
}
int
console_out(int c)
{
int rc;
const os_time_t timeout =
os_time_ms_to_ticks32(MYNEWT_VAL(CONSOLE_DEFAULT_LOCK_TIMEOUT));
if (console_lock(timeout) != OS_OK) {
return c;
}
console_switch_to_logs();
rc = console_filter_out(c);
(void)console_unlock();
return rc;
}
void
console_write(const char *str, int cnt)
{
const os_time_t timeout =
os_time_ms_to_ticks32(MYNEWT_VAL(CONSOLE_DEFAULT_LOCK_TIMEOUT));
if (console_lock(timeout) != OS_OK) {
return;
}
if (cnt >= 2 && str[0] == CONSOLE_NLIP_DATA_START1 &&
str[1] == CONSOLE_NLIP_DATA_START2) {
g_is_output_nlip = 1;
}
/* From the shell the first byte is always \n followed by the
* actual pkt start bytes, hence checking byte 1 and 2
*/
if (cnt >= 3 && str[1] == CONSOLE_NLIP_PKT_START1 &&
str[2] == CONSOLE_NLIP_PKT_START2) {
g_is_output_nlip = 1;
}
/* If the byte string is non nlip and we are silencing non nlip bytes,
* do not let it go out on the console
*/
if (!g_is_output_nlip && g_console_silence_non_nlip) {
goto done;
}
console_switch_to_logs();
console_filter_write(str, cnt);
done:
if (cnt > 0 && str[cnt - 1] == '\n') {
g_is_output_nlip = 0;
}
(void)console_unlock();
}
#if MYNEWT_VAL(CONSOLE_COMPAT)
int
console_read(char *str, int cnt, int *newline)
{
struct os_event *ev;
struct console_input *cmd;
size_t len;
*newline = 0;
ev = os_eventq_get_no_wait(lines_queue);
if (!ev) {
return 0;
}
cmd = ev->ev_arg;
len = strlen(cmd->line);
if ((cnt - 1) < len) {
len = cnt - 1;
}
if (len > 0) {
memcpy(str, cmd->line, len);
str[len] = '\0';
} else {
str[0] = cmd->line[0];
}
console_line_event_put(ev);
*newline = 1;
return len;
}
#endif
void
console_blocking_mode(void)
{
#if MYNEWT_VAL(CONSOLE_UART)
uart_console_blocking_mode();
#endif
}
void
console_non_blocking_mode(void)
{
#if MYNEWT_VAL(CONSOLE_UART)
uart_console_non_blocking_mode();
#endif
}
static inline void
cursor_forward(unsigned int count)
{
char seq[14] = CSI;
char *p;
if (count) {
p = add_ascii_num_with_char(seq + 2, count, 'C');
console_write_nolock(seq, p - seq);
}
}
static inline void
cursor_backward(unsigned int count)
{
char seq[14] = CSI;
char *p;
if (count) {
p = add_ascii_num_with_char(seq + 2, count, 'D');
console_write_nolock(seq, p - seq);
}
}
static bool trailing_selection;
/*
* The following two weak functions are here in case linker does not allow
* to eliminate dead code (console_history_search()).
*/
history_handle_t __attribute__((weak))
console_history_find(history_handle_t start, history_find_type_t search_type,
void *arg)
{
return (history_handle_t )0;
}
int __attribute__((weak))
console_history_get(history_handle_t handle, size_t offset, char *buf,
size_t buf_size)
{
return 0;
}
static void
console_history_search(char *line, history_find_type_t direction)
{
line[cur] = '\0';
history_line = console_history_find(history_line, direction, line);
/* No more lines backward, do nothing */
cursor_clear_line();
if (!history_line) {
trailing_chars = 0;
} else {
trailing_chars = console_history_get(history_line, cur,line + cur,
MYNEWT_VAL(CONSOLE_MAX_INPUT_LEN) - cur);
console_write_str(CSI "7m");
console_write_nolock(line + cur, trailing_chars);
console_write_str(CSI "0m");
cursor_backward(trailing_chars);
trailing_selection = true;
}
}
static void
insert_char(char *pos, char c)
{
char tmp;
int end;
if ((!MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) || !trailing_selection) &&
cur + trailing_chars >= MYNEWT_VAL(CONSOLE_MAX_INPUT_LEN) - 1) {
return;
}
if (echo) {
if (!MYNEWT_VAL_CHOICE(CONSOLE_HISTORY, none) &&
MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) && trailing_selection) {
cursor_clear_line();
trailing_chars = 0;
trailing_selection = false;
}
/* Echo back to console */
console_out_nolock(c);
}
++cur;
if (trailing_chars == 0) {
*pos = c;
if (!MYNEWT_VAL_CHOICE(CONSOLE_HISTORY, none) &&
MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) && cur > 1) {
history_line = 0;
console_history_search(pos - cur + 1, HFT_MATCH_PREV);
return;
}
}
tmp = *pos;
*(pos++) = c;
end = trailing_chars;
while (end-- > 0) {
if (echo) {
console_out_nolock(tmp);
}
c = *pos;
*(pos++) = tmp;
tmp = c;
}
/* Move cursor back to right place */
if (echo) {
cursor_backward(trailing_chars);
}
}
static void
clear_selection(const char *line)
{
if (MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) && trailing_selection) {
trailing_selection = false;
console_write_str(CSI "0m");
console_write_nolock(line + cur, trailing_chars);
cursor_backward(trailing_chars);
}
}
static void
delete_selection(char *pos)
{
if (MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) && trailing_selection) {
*pos = 0;
trailing_selection = false;
trailing_chars = 0;
cursor_clear_line();
}
}
/* Delete character at cursor position */
static void
del_char(char *pos)
{
int left;
delete_selection(pos);
left = trailing_chars;
while (left-- > 1) {
*pos = *(pos + 1);
console_out_nolock(*(pos++));
}
/* Move cursor back to right place */
if (trailing_chars) {
console_out_nolock(' ');
cursor_backward(trailing_chars);
trailing_chars--;
}
}
static void
console_clear_line(void)
{
if (cur) {
cursor_backward(cur);
}
cur = 0;
trailing_chars = 0;
cursor_clear_line();
}
#if !MYNEWT_VAL_CHOICE(CONSOLE_HISTORY, none)
static void
console_history_move(char *line, history_find_type_t direction)
{
history_handle_t new_line;
new_line = console_history_find(history_line, direction, NULL);
/* No more lines backward, do nothing */
if (!new_line && direction == HFT_PREV) {
return;
}
history_line = new_line;
console_clear_line();
if (new_line) {
cur = console_history_get(new_line, 0, line,
MYNEWT_VAL_CONSOLE_MAX_INPUT_LEN);
} else {
line[0] = '\0';
}
trailing_chars = 0;
console_write_nolock(line, cur);
}
static void
console_handle_move(char *line, int direction)
{
if (MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) && trailing_selection) {
console_history_search(line, direction > 0 ? HFT_MATCH_PREV :
HFT_MATCH_NEXT);
} else {
console_history_move(line, direction > 0 ? HFT_PREV : HFT_NEXT);
}
}
#endif
static void
handle_home(char *line)
{
clear_selection(line);
if (cur) {
cursor_backward(cur);
trailing_chars += cur;
cur = 0;
}
}
static void
handle_delete(char *line)
{
if (trailing_chars) {
del_char(&line[cur]);
}
}
static void
handle_end(char *line)
{
clear_selection(line);
if (trailing_chars) {
cursor_forward(trailing_chars);
cur += trailing_chars;
trailing_chars = 0;
}
}
static void
handle_ansi(uint8_t byte, char *line)
{
if (esc_state & ESC_ANSI_FIRST) {
esc_state &= ~ESC_ANSI_FIRST;
if (!isdigit(byte)) {
ansi_val = 1;
goto ansi_cmd;
}
esc_state |= ESC_ANSI_VAL;
ansi_val = byte - '0';
ansi_val_2 = 0;
return;
}
if (esc_state & ESC_ANSI_VAL) {
if (isdigit(byte)) {
if (esc_state & ESC_ANSI_VAL_2) {
ansi_val_2 *= 10;
ansi_val_2 += byte - '0';
} else {
ansi_val *= 10;
ansi_val += byte - '0';
}
return;
}
/* Multi value sequence, e.g. Esc[Line;ColumnH */
if (byte == ';' && !(esc_state & ESC_ANSI_VAL_2)) {
esc_state |= ESC_ANSI_VAL_2;
return;
}
esc_state &= ~ESC_ANSI_VAL;
esc_state &= ~ESC_ANSI_VAL_2;
}
ansi_cmd:
switch (byte) {
#if !MYNEWT_VAL_CHOICE(CONSOLE_HISTORY, none)
case ANSI_UP:
case ANSI_DOWN:
console_handle_move(line, byte == ANSI_UP ? 1 : -1);
break;
#endif
case ANSI_BACKWARD:
if (ansi_val > cur) {
break;
}
clear_selection(line);
trailing_chars += ansi_val;
cur -= ansi_val;
cursor_backward(ansi_val);
break;
case ANSI_FORWARD:
if (ansi_val > trailing_chars) {
break;
}
clear_selection(line);
trailing_chars -= ansi_val;
cur += ansi_val;
cursor_forward(ansi_val);
break;
case ANSI_HOME:
handle_home(line);
break;
case ANSI_END:
handle_end(line);
break;
case '~':
switch (ansi_val) {
case 1:
handle_home(line);
break;
case 3:
handle_delete(line);
break;
case 4:
handle_end(line);
break;
}
break;
case DSR_CPS:
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) && terminal_size_requested) {
terminal_size_requested = false;
max_row = ansi_val;
console_cursor_set(max_row - 1, 1);
cursor_save();
console_set_scrolling_region(1, max_row - 1);
if (prompt_len) {
console_cursor_set(max_row, 1);
console_write_nolock(console_prompt, prompt_len);
console_write_nolock(line, cur + trailing_chars);
console_write_str(CSI "K");
cursor_backward(trailing_chars);
}
cursor_restore();
}
break;
default:
break;
}
esc_state &= ~ESC_ANSI;
}
static void
console_handle_line(void)
{
cur = 0;
trailing_chars = 0;
os_eventq_put(lines_queue, current_line_ev);
#if MYNEWT_VAL(CONSOLE_COMPAT)
if (console_compat_rx_cb) {
console_compat_rx_cb();
}
#endif
current_line_ev = NULL;
}
static int
handle_nlip(uint8_t byte)
{
int handled;
struct console_input *input;
input = current_line_ev->ev_arg;
handled = 1;
switch (nlip_state) {
case NLIP_PKT_START2:
case NLIP_DATA_START2:
insert_char(&input->line[cur], byte);
if (byte == '\n') {
input->line[cur] = '\0';
console_echo(1);
nlip_state = 0;
console_handle_line();
}
break;
case NLIP_PKT_START1:
if (byte == CONSOLE_NLIP_PKT_START2) {
nlip_state = NLIP_PKT_START2;
/* Disable echo to not flood the UART */
console_echo(0);
insert_char(&input->line[cur], CONSOLE_NLIP_PKT_START1);
insert_char(&input->line[cur], CONSOLE_NLIP_PKT_START2);
} else {
nlip_state = 0;
handled = g_console_ignore_non_nlip;
}
break;
case NLIP_DATA_START1:
if (byte == CONSOLE_NLIP_DATA_START2) {
nlip_state = NLIP_DATA_START2;
/* Disable echo to not flood the UART */
console_echo(0);
insert_char(&input->line[cur], CONSOLE_NLIP_DATA_START1);
insert_char(&input->line[cur], CONSOLE_NLIP_DATA_START2);
} else {
nlip_state = 0;
handled = g_console_ignore_non_nlip;
}
break;
default:
if (byte == CONSOLE_NLIP_DATA_START1) {
nlip_state = NLIP_DATA_START1;
} else if (byte == CONSOLE_NLIP_PKT_START1) {
nlip_state = NLIP_PKT_START1;
} else {
/* For old code compatibility end of lines characters pass through */
handled = g_console_ignore_non_nlip && byte != '\r' && byte != '\n';
}
break;
}
return handled;
}
static int
console_append_char(char *line, uint8_t byte)
{
if (cur + trailing_chars >= MYNEWT_VAL(CONSOLE_MAX_INPUT_LEN) - 1) {
return 0;
}
line[cur + trailing_chars] = byte;
if (byte == '\0') {
return 1;
}
if (echo) {
/* Echo back to console */
console_switch_to_prompt();
console_out_nolock(byte);
console_switch_to_logs();
}
++cur;
return 1;
}
static void
handle_backspace(char *line)
{
if (cur > 0) {
cursor_backward(1);
cur--;
trailing_chars++;
del_char(&line[cur]);
}
}
int
console_handle_char(uint8_t byte)
{
#if !MYNEWT_VAL(CONSOLE_INPUT)
return 0;
#endif
struct console_input *input;
static char prev_endl = '\0';
if (!lines_queue) {
return 0;
}
if (!current_line_ev) {
current_line_ev = os_eventq_get_no_wait(&avail_queue);
if (!current_line_ev) {
rx_stalled = true;
return -1;
}
}
input = current_line_ev->ev_arg;
if (handle_nlip(byte)) {
return 0;
}
if (console_lock(1000)) {
return -1;
}
console_switch_to_prompt();
/* Handle ANSI escape mode */
if (esc_state & ESC_ANSI) {
handle_ansi(byte, input->line);
goto unlock;
}
/* Handle escape mode */
if (esc_state & ESC_ESC) {
esc_state &= ~ESC_ESC;
handle_ansi(byte, input->line);
switch (byte) {
case ANSI_ESC:
esc_state |= ESC_ANSI;
esc_state |= ESC_ANSI_FIRST;
break;
default:
break;
}
goto unlock;
}
/* Handle special control characters */
if (!isprint(byte)) {
handle_ansi(byte, input->line);
switch (byte) {
case DEL:
case BS:
handle_backspace(input->line);
break;
case ESC:
esc_state |= ESC_ESC;
break;
default:
insert_char(&input->line[cur], byte);
/* Falls through. */
case '\r':
/* Falls through. */
case '\n':
if (byte == '\n' && prev_endl == '\r') {
/* handle double end line chars */
prev_endl = byte;
break;
}
prev_endl = byte;
input->line[cur + trailing_chars] = '\0';
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY) && prompt_has_focus) {
console_switch_to_logs();
/*
* Cursor is always in the middle of the line since new lines
* are held, so new line is needed here.
*/
console_out_nolock('\n');
/* Duplicate command line to log space */
console_write_str(console_prompt);
console_write_nolock(input->line, cur + trailing_chars);
/*
* Add additional new line if log line was not finished,
* this will avoid printing outstanding line at the end
* of duplicated command line.
*/
if (console_is_midline) {
console_out_nolock('\n');
}
console_switch_to_prompt();
console_clear_line();
} else {
console_filter_out('\r');
console_filter_out('\n');
}
if (!MYNEWT_VAL_CHOICE(CONSOLE_HISTORY, none)) {
console_history_add(input->line);
history_line = 0;
if (MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH)) {
trailing_selection = 0;
}
}
console_handle_line();
break;
case '\t':
if (completion && (!trailing_chars ||
(MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) && trailing_selection))) {
if (MYNEWT_VAL(CONSOLE_HISTORY_AUTO_SEARCH) && trailing_selection) {
delete_selection(input->line + cur);
}
#if MYNEWT_VAL(CONSOLE_UART_RX_BUF_SIZE) == 0
console_blocking_mode();
#endif
console_switch_to_logs();
completion(input->line, console_append_char);
console_switch_to_prompt();
#if MYNEWT_VAL(CONSOLE_UART_RX_BUF_SIZE) == 0
console_non_blocking_mode();
#endif
}
break;
/* CTRL-C */
case ETX:
console_clear_line();
break;
/* CTRL-L */
case VT:
if (MYNEWT_VAL(CONSOLE_PROMPT_STICKY)) {
request_terminal_size();
} else {
console_out_nolock(VT);
}
break;
}
} else {
insert_char(&input->line[cur], byte);
}
unlock:
(void)console_unlock();
return 0;
}
int
console_is_init(void)
{
#if MYNEWT_VAL(CONSOLE_UART)
return uart_console_is_init();
#endif
#if MYNEWT_VAL(CONSOLE_RTT)
return rtt_console_is_init();
#endif
#if MYNEWT_VAL(CONSOLE_BLE_MONITOR)
return ble_monitor_console_is_init();
#endif
return 0;
}
void
console_line_queue_set(struct os_eventq *evq)
{
lines_queue = evq;
}
void
console_line_event_put(struct os_event *ev)
{
os_eventq_put(&avail_queue, ev);
if (rx_stalled) {
rx_stalled = false;
console_rx_restart();
}
}
void
console_set_completion_cb(completion_cb cb)
{
completion = cb;
}
void
console_deinit(void)
{
#if MYNEWT_VAL(CONSOLE_UART)
uart_console_deinit();
#endif
}
void
console_reinit(void)
{
#if MYNEWT_VAL(CONSOLE_UART)
uart_console_init();
#endif
}
#if MYNEWT_VAL(CONSOLE_COMPAT)
int
console_init(console_rx_cb rx_cb)
{
int i;
os_eventq_init(&compat_lines_queue);
os_eventq_init(&compat_avail_queue);
console_line_queue_set(&compat_lines_queue);
for (i = 0; i < CONSOLE_COMPAT_MAX_CMD_QUEUED; i++) {
shell_console_ev[i].ev_arg = &buf[i];
console_line_event_put(&shell_console_ev[i]);
}
console_compat_rx_cb = rx_cb;
return 0;
}
#endif
void
console_pkg_init(void)
{
int rc = 0;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
os_eventq_init(&avail_queue);
os_mutex_init(&console_write_lock);
#if MYNEWT_VAL(CONSOLE_UART)
rc = uart_console_init();
#endif
#if MYNEWT_VAL(CONSOLE_RTT)
rc = rtt_console_init();
#endif
SYSINIT_PANIC_ASSERT(rc == 0);
}