apps/system/cle: Usage improvements including command line history (taken from readline) and support for addition control characters.

This commit is contained in:
mst 2018-08-05 16:05:59 -06:00 committed by Gregory Nutt
parent f5eeaf1fb6
commit 62134a0419
3 changed files with 294 additions and 6 deletions

View File

@ -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

View File

@ -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;
} }

View File

@ -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"