apps/system/cle: Usage improvements including command line history (taken from readline) and support for addition control characters.
This commit is contained in:
parent
f5eeaf1fb6
commit
62134a0419
@ -21,6 +21,50 @@ menuconfig SYSTEM_CLE
|
|||||||
|
|
||||||
if SYSTEM_CLE
|
if SYSTEM_CLE
|
||||||
|
|
||||||
|
config SYSTEM_CLE_CMD_HISTORY
|
||||||
|
bool "Command line history"
|
||||||
|
default n
|
||||||
|
---help---
|
||||||
|
Build in support for Unix-style command history using up and down
|
||||||
|
arrow keys. This feature was originally provided by Nghia Ho.
|
||||||
|
|
||||||
|
NOTE: Command line history is kept in an in-memory array and is
|
||||||
|
shared. In the FLAT or PROTECTED builds, this history is shared by
|
||||||
|
all threads; in the KERNEL build, the command line history is shared
|
||||||
|
by all threads in the process. This means that in a FLAT build, for
|
||||||
|
example, a built-in application started from NSH will have the same
|
||||||
|
history as does NSH if it also uses the CLE. This also means
|
||||||
|
that different NSH sessions on serial, USB, or Telnet will also
|
||||||
|
share the same history array.
|
||||||
|
|
||||||
|
In a KERNEL build, each process will have a separately allocated
|
||||||
|
history array so the issue is lessened. History would still be
|
||||||
|
shared amount pthreads within the same process, however.
|
||||||
|
|
||||||
|
if SYSTEM_CLE_CMD_HISTORY
|
||||||
|
|
||||||
|
config SYSTEM_CLE_CMD_HISTORY_LINELEN
|
||||||
|
int "Command line history length"
|
||||||
|
default 64 if DEFAULT_SMALL
|
||||||
|
default 80 if !DEFAULT_SMALL
|
||||||
|
---help---
|
||||||
|
The maximum length of one command line in the in-memory array. The
|
||||||
|
total memory usage for the command line array will be
|
||||||
|
SYSTEM_CLE_CMD_HISTORY_LINELEN x SYSTEM_CLE_CMD_HISTORY_LEN. Default:
|
||||||
|
64/80
|
||||||
|
|
||||||
|
config SYSTEM_CLE_CMD_HISTORY_LEN
|
||||||
|
int "Command line history records"
|
||||||
|
default 4 if DEFAULT_SMALL
|
||||||
|
default 16 if !DEFAULT_SMALL
|
||||||
|
---help---
|
||||||
|
The number of lines of history that will be buffered in the in-
|
||||||
|
memory array. The total memory usage for the command line array
|
||||||
|
will be SYSTEM_CLE_CMD_HISTORY_LINELEN x SYSTEM_CLE_CMD_HISTORY_LEN.
|
||||||
|
Default: 16
|
||||||
|
|
||||||
|
endif # SYSTEM_CLE_CMD_HISTORY
|
||||||
|
|
||||||
config SYSTEM_CLE_DEBUGLEVEL
|
config SYSTEM_CLE_DEBUGLEVEL
|
||||||
int "Debug level"
|
int "Debug level"
|
||||||
default 0
|
default 0
|
||||||
|
250
system/cle/cle.c
250
system/cle/cle.c
@ -1,7 +1,7 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* apps/system/cle/cle.c
|
* apps/system/cle/cle.c
|
||||||
*
|
*
|
||||||
* Copyright (C) 2014 Gregory Nutt. All rights reserved.
|
* Copyright (C) 2014, 2018 Gregory Nutt. All rights reserved.
|
||||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@ -134,6 +134,30 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_SYSTEM_CLE_CMD_HISTORY
|
||||||
|
/* Command history
|
||||||
|
*
|
||||||
|
* g_cmd_history[][] Circular buffer
|
||||||
|
* g_cmd_history_head Head of the circular buffer, most recent
|
||||||
|
* command
|
||||||
|
* g_cmd_history_steps_from_head Offset from head
|
||||||
|
* g_cmd_history_len Number of elements in the circular buffer
|
||||||
|
*
|
||||||
|
* REVISIT: These globals will *not* work in an environment where there
|
||||||
|
* are multiple copies if the NSH shell! Use of global variables is not
|
||||||
|
* thread safe! These settings should, at least, be semaphore protected so
|
||||||
|
* that the integrity of the data is assured, even though commands from
|
||||||
|
* different sessions may be intermixed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static char g_cmd_history[CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN]
|
||||||
|
[CONFIG_SYSTEM_CLE_CMD_HISTORY_LINELEN];
|
||||||
|
static int g_cmd_history_head = -1;
|
||||||
|
static int g_cmd_history_steps_from_head = 1;
|
||||||
|
static int g_cmd_history_len = 0;
|
||||||
|
|
||||||
|
#endif /* CONFIG_SYSTEM_CLE_CMD_HISTORY */
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Private Types
|
* Private Types
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@ -148,8 +172,10 @@ enum cle_key_e
|
|||||||
KEY_RIGHT = CTRL('F'), /* Move right one character */
|
KEY_RIGHT = CTRL('F'), /* Move right one character */
|
||||||
KEY_DELLEFT = CTRL('H'), /* Delete character, left (backspace) */
|
KEY_DELLEFT = CTRL('H'), /* Delete character, left (backspace) */
|
||||||
KEY_DELEOL = CTRL('K'), /* Delete to the end of the line */
|
KEY_DELEOL = CTRL('K'), /* Delete to the end of the line */
|
||||||
|
KEY_DN = CTRL('N'), /* Cursor down */
|
||||||
|
KEY_UP = CTRL('P'), /* Cursor up */
|
||||||
KEY_DELLINE = CTRL('U'), /* Delete the entire line */
|
KEY_DELLINE = CTRL('U'), /* Delete the entire line */
|
||||||
KEY_QUOTE = '\\', /* The next character is quote (use literal value) */
|
KEY_QUOTE = '\\' /* The next character is quote (use literal value) */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This structure describes the overall state of the editor */
|
/* This structure describes the overall state of the editor */
|
||||||
@ -250,8 +276,6 @@ static void cle_write(FAR struct cle_s *priv, FAR const char *buffer,
|
|||||||
ssize_t nwritten;
|
ssize_t nwritten;
|
||||||
uint16_t nremaining = buflen;
|
uint16_t nremaining = buflen;
|
||||||
|
|
||||||
//cleinfo("buffer=%p buflen=%d\n", buffer, (int)buflen);
|
|
||||||
|
|
||||||
/* Loop until all bytes have been successfully written (or until a
|
/* Loop until all bytes have been successfully written (or until a
|
||||||
* unrecoverable error is encountered)
|
* unrecoverable error is encountered)
|
||||||
*/
|
*/
|
||||||
@ -749,14 +773,202 @@ static int cle_editloop(FAR struct cle_s *priv)
|
|||||||
|
|
||||||
/* Get the next character from the input */
|
/* Get the next character from the input */
|
||||||
|
|
||||||
|
#if 1 /* Perhaps here should be a config switch */
|
||||||
|
/* Simple decode of some VT100/xterm codes: left/right, up/dn,
|
||||||
|
* home/end, del
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
char state = 0;
|
||||||
|
|
||||||
|
/* loop till we have a ch */
|
||||||
|
|
||||||
|
for (; ; )
|
||||||
|
{
|
||||||
|
ch = cle_getch(priv);
|
||||||
|
if (ch < 0)
|
||||||
|
{
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
else if (state != 0)
|
||||||
|
{
|
||||||
|
if (state == (char)1) /* Got ESC */
|
||||||
|
{
|
||||||
|
if (ch == '[' || ch == 'O')
|
||||||
|
{
|
||||||
|
state = ch;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break; /* break the for loop */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (state == '[')
|
||||||
|
{
|
||||||
|
/* Got ESC[ */
|
||||||
|
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '3': /* ESC[3~ = DEL */
|
||||||
|
{
|
||||||
|
state = ch;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case 'A':
|
||||||
|
{
|
||||||
|
ch = KEY_UP;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B':
|
||||||
|
{
|
||||||
|
ch = KEY_DN;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'C':
|
||||||
|
{
|
||||||
|
ch = KEY_RIGHT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D':
|
||||||
|
{
|
||||||
|
ch = KEY_LEFT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'F':
|
||||||
|
{
|
||||||
|
ch = KEY_ENDLINE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'H':
|
||||||
|
{
|
||||||
|
ch = KEY_BEGINLINE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break; /* Break the 'for' loop */
|
||||||
|
}
|
||||||
|
else if (state == 'O')
|
||||||
|
{
|
||||||
|
/* got ESCO */
|
||||||
|
|
||||||
|
if (ch=='F')
|
||||||
|
{
|
||||||
|
ch = KEY_ENDLINE;
|
||||||
|
}
|
||||||
|
break; /* Break the 'for' loop */
|
||||||
|
}
|
||||||
|
else if (state == '3')
|
||||||
|
{
|
||||||
|
if (ch == '~')
|
||||||
|
{
|
||||||
|
ch = KEY_DEL;
|
||||||
|
}
|
||||||
|
break; /* Break the 'for' loop */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break; /* Break the 'for' loop */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ch == ASCII_ESC)
|
||||||
|
{
|
||||||
|
++state;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break; /* Break the 'for' loop, use the char */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
ch = cle_getch(priv);
|
ch = cle_getch(priv);
|
||||||
if (ch < 0)
|
if (ch < 0)
|
||||||
{
|
{
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Then handle the character. */
|
/* Then handle the character. */
|
||||||
|
|
||||||
|
#ifdef CONFIG_SYSTEM_CLE_CMD_HISTORY
|
||||||
|
if (g_cmd_history_len > 0)
|
||||||
|
{
|
||||||
|
int i = 1;
|
||||||
|
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case KEY_UP:
|
||||||
|
/* Go to the past command in history */
|
||||||
|
|
||||||
|
g_cmd_history_steps_from_head--;
|
||||||
|
|
||||||
|
if (-g_cmd_history_steps_from_head >= g_cmd_history_len)
|
||||||
|
{
|
||||||
|
g_cmd_history_steps_from_head = -(g_cmd_history_len - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_DN:
|
||||||
|
/* Go to the recent command in history */
|
||||||
|
|
||||||
|
g_cmd_history_steps_from_head++;
|
||||||
|
|
||||||
|
if (g_cmd_history_steps_from_head > 1)
|
||||||
|
{
|
||||||
|
g_cmd_history_steps_from_head = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
i = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != 0)
|
||||||
|
{
|
||||||
|
priv->nchars = 0;
|
||||||
|
priv->curpos = 0;
|
||||||
|
|
||||||
|
if (g_cmd_history_steps_from_head != 1)
|
||||||
|
{
|
||||||
|
int idx = g_cmd_history_head +
|
||||||
|
g_cmd_history_steps_from_head;
|
||||||
|
|
||||||
|
/* Circular buffer wrap around */
|
||||||
|
|
||||||
|
if (idx < 0)
|
||||||
|
{
|
||||||
|
idx = idx + CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN;
|
||||||
|
}
|
||||||
|
else if (idx >= CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN)
|
||||||
|
{
|
||||||
|
idx = idx - CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; g_cmd_history[idx][i] != '\0'; i++)
|
||||||
|
{
|
||||||
|
cle_insertch(priv, g_cmd_history[idx][i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->curpos = priv->nchars;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_SYSTEM_CLE_CMD_HISTORY */
|
||||||
|
|
||||||
switch (ch)
|
switch (ch)
|
||||||
{
|
{
|
||||||
case KEY_BEGINLINE: /* Move cursor to start of current line */
|
case KEY_BEGINLINE: /* Move cursor to start of current line */
|
||||||
@ -962,5 +1174,35 @@ int cle(FAR char *line, uint16_t linelen, FILE *instream, FILE *outstream)
|
|||||||
/* Make sure that the line is NUL terminated */
|
/* Make sure that the line is NUL terminated */
|
||||||
|
|
||||||
line[priv.nchars] = '\0';
|
line[priv.nchars] = '\0';
|
||||||
|
|
||||||
|
#ifdef CONFIG_SYSTEM_CLE_CMD_HISTORY
|
||||||
|
/* Save history of command, only if there was something typed besides
|
||||||
|
* return character.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (priv.nchars > 1)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
g_cmd_history_head =
|
||||||
|
(g_cmd_history_head + 1) % CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN;
|
||||||
|
|
||||||
|
for (i = 0;
|
||||||
|
(i < priv.nchars - 1) && i < (CONFIG_SYSTEM_CLE_CMD_HISTORY_LINELEN - 1);
|
||||||
|
i++)
|
||||||
|
{
|
||||||
|
g_cmd_history[g_cmd_history_head][i] = line[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cmd_history[g_cmd_history_head][i] = '\0';
|
||||||
|
g_cmd_history_steps_from_head = 1;
|
||||||
|
|
||||||
|
if (g_cmd_history_len < CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN)
|
||||||
|
{
|
||||||
|
g_cmd_history_len++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_SYSTEM_CLE_CMD_HISTORY */
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,8 @@ config READLINE_CMD_HISTORY
|
|||||||
share the same history array.
|
share the same history array.
|
||||||
|
|
||||||
In a KERNEL build, each process will have a separately allocated
|
In a KERNEL build, each process will have a separately allocated
|
||||||
history array so the issue is lessened.
|
history array so the issue is lessened. History would still be
|
||||||
|
shared amount pthreads within the same process, however.
|
||||||
|
|
||||||
if READLINE_CMD_HISTORY
|
if READLINE_CMD_HISTORY
|
||||||
|
|
||||||
@ -82,7 +83,8 @@ config READLINE_CMD_HISTORY_LINELEN
|
|||||||
---help---
|
---help---
|
||||||
The maximum length of one command line in the in-memory array. The
|
The maximum length of one command line in the in-memory array. The
|
||||||
total memory usage for the command line array will be
|
total memory usage for the command line array will be
|
||||||
READLINE_CMD_HISTORY_LINELEN x READLINE_CMD_HISTORY_LEN. Default: 64/80
|
READLINE_CMD_HISTORY_LINELEN x READLINE_CMD_HISTORY_LEN. Default:
|
||||||
|
64/80
|
||||||
|
|
||||||
config READLINE_CMD_HISTORY_LEN
|
config READLINE_CMD_HISTORY_LEN
|
||||||
int "Command line history records"
|
int "Command line history records"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user