1243 lines
33 KiB
C
1243 lines
33 KiB
C
/****************************************************************************
|
|
* apps/system/cle/cle.c
|
|
*
|
|
* Copyright (C) 2014, 2018-2019 Gregory Nutt. All rights reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <inttypes.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <syslog.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/ascii.h>
|
|
#include <nuttx/vt100.h>
|
|
|
|
#include "system/cle.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Some environments may return CR as end-of-line, others LF, and others
|
|
* both. If not specified, the logic here assumes either (but not both) as
|
|
* the default.
|
|
*/
|
|
|
|
#if defined(CONFIG_EOL_IS_CR)
|
|
# undef CONFIG_EOL_IS_LF
|
|
# undef CONFIG_EOL_IS_BOTH_CRLF
|
|
# undef CONFIG_EOL_IS_EITHER_CRLF
|
|
#elif defined(CONFIG_EOL_IS_LF)
|
|
# undef CONFIG_EOL_IS_CR
|
|
# undef CONFIG_EOL_IS_BOTH_CRLF
|
|
# undef CONFIG_EOL_IS_EITHER_CRLF
|
|
#elif defined(CONFIG_EOL_IS_BOTH_CRLF)
|
|
# undef CONFIG_EOL_IS_CR
|
|
# undef CONFIG_EOL_IS_LF
|
|
# undef CONFIG_EOL_IS_EITHER_CRLF
|
|
#elif defined(CONFIG_EOL_IS_EITHER_CRLF)
|
|
# undef CONFIG_EOL_IS_CR
|
|
# undef CONFIG_EOL_IS_LF
|
|
# undef CONFIG_EOL_IS_BOTH_CRLF
|
|
#else
|
|
# undef CONFIG_EOL_IS_CR
|
|
# undef CONFIG_EOL_IS_LF
|
|
# undef CONFIG_EOL_IS_BOTH_CRLF
|
|
# define CONFIG_EOL_IS_EITHER_CRLF 1
|
|
#endif
|
|
|
|
/* Control characters */
|
|
|
|
#undef CTRL
|
|
#define CTRL(a) ((a) & 0x1f)
|
|
|
|
#define CLE_BEL(priv) cle_putch(priv,CTRL('G'))
|
|
|
|
/* Sizes of things */
|
|
|
|
#define TABSIZE 8 /* A TAB is eight characters */
|
|
#define TABMASK 7 /* Mask for TAB alignment */
|
|
#define NEXT_TAB(p) (((p) + TABSIZE) & ~TABMASK)
|
|
|
|
/* Debug */
|
|
|
|
#ifndef CONFIG_SYSTEM_CLE_DEBUGLEVEL
|
|
# define CONFIG_SYSTEM_CLE_DEBUGLEVEL 0
|
|
#endif
|
|
|
|
#if CONFIG_SYSTEM_CLE_DEBUGLEVEL > 0
|
|
# define cledbg cle_debug
|
|
#else
|
|
# define cledbg _none
|
|
#endif
|
|
|
|
#if CONFIG_SYSTEM_CLE_DEBUGLEVEL > 1
|
|
# define cleinfo cle_debug
|
|
#else
|
|
# define cleinfo _none
|
|
#endif
|
|
|
|
#ifdef CONFIG_SYSTEM_COLOR_CLE
|
|
# define COLOR_PROMPT VT100_YELLOW
|
|
# define COLOR_COMMAND VT100_CYAN
|
|
# define COLOR_OUTPUT VT100_GREEN
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* VI Key Bindings */
|
|
|
|
enum cle_key_e
|
|
{
|
|
KEY_BEGINLINE = CTRL('A'), /* Move cursor to start of current line */
|
|
KEY_LEFT = CTRL('B'), /* Move left one character */
|
|
KEY_DEL = CTRL('D'), /* Delete a single character at the cursor position */
|
|
KEY_ENDLINE = CTRL('E'), /* Move cursor to end of current line */
|
|
KEY_RIGHT = CTRL('F'), /* Move right one character */
|
|
KEY_DELLEFT = CTRL('H'), /* Delete character, left (backspace) */
|
|
KEY_DELEOL = CTRL('K'), /* Delete to the end of the line */
|
|
KEY_CLRSCR = CTRL('L'), /* Clear the screen */
|
|
KEY_DN = CTRL('N'), /* Cursor down */
|
|
KEY_UP = CTRL('P'), /* Cursor up */
|
|
KEY_DELLINE = CTRL('U'), /* Delete the entire line */
|
|
KEY_QUOTE = '\\' /* The next character is quote (use literal value) */
|
|
};
|
|
|
|
/* This structure describes the overall state of the editor */
|
|
|
|
struct cle_s
|
|
{
|
|
uint16_t curpos; /* Current cursor position */
|
|
uint16_t cursave; /* Saved cursor position */
|
|
uint16_t row; /* This is the row that we are editing in */
|
|
uint16_t coloffs; /* Left cursor offset */
|
|
uint16_t linelen; /* Size of the line buffer */
|
|
uint16_t nchars; /* Size of data in the line buffer */
|
|
FAR FILE *ins; /* Input file stream */
|
|
FAR FILE *outs; /* Output file stream */
|
|
FAR char *line; /* Line buffer */
|
|
FAR const char *prompt; /* Prompt, in case we have to re-print it */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
#if CONFIG_SYSTEM_CLE_DEBUGLEVEL > 0
|
|
static void cle_debug(FAR const char *fmt, ...);
|
|
#endif
|
|
|
|
/* Low-level display and data entry functions */
|
|
|
|
static void cle_write(FAR struct cle_s *priv, FAR const char *buffer,
|
|
uint16_t buflen);
|
|
static void cle_putch(FAR struct cle_s *priv, char ch);
|
|
static int cle_getch(FAR struct cle_s *priv);
|
|
static void cle_cursoron(FAR struct cle_s *priv);
|
|
static void cle_cursoroff(FAR struct cle_s *priv);
|
|
static void cle_setcursor(FAR struct cle_s *priv, uint16_t column);
|
|
static int cle_getcursor(FAR struct cle_s *priv, uint16_t *prow,
|
|
uint16_t *pcolumn);
|
|
static void cle_clrtoeol(FAR struct cle_s *priv);
|
|
|
|
/* Editor function */
|
|
|
|
static bool cle_opentext(FAR struct cle_s *priv, uint16_t pos,
|
|
uint16_t increment);
|
|
static void cle_closetext(FAR struct cle_s *priv, uint16_t pos,
|
|
uint16_t size);
|
|
static void cle_showtext(FAR struct cle_s *priv);
|
|
static void cle_insertch(FAR struct cle_s *priv, char ch);
|
|
static int cle_editloop(FAR struct cle_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
#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 */
|
|
|
|
/* VT100 escape sequences */
|
|
|
|
static const char g_cursoron[] = VT100_CURSORON;
|
|
static const char g_cursoroff[] = VT100_CURSOROFF;
|
|
static const char g_getcursor[] = VT100_GETCURSOR;
|
|
static const char g_erasetoeol[] = VT100_CLEAREOL;
|
|
static const char g_fmtcursorpos[] = VT100_FMT_CURSORPOS;
|
|
static const char g_clrscr[] = VT100_CLEARSCREEN;
|
|
static const char g_home[] = VT100_CURSORHOME;
|
|
#ifdef CONFIG_SYSTEM_COLOR_CLE
|
|
static const char g_setcolor[] = VT100_FMT_FORE_COLOR;
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: cle_debug
|
|
*
|
|
* Description:
|
|
* Print a debug message to the syslog
|
|
*
|
|
****************************************************************************/
|
|
|
|
#if CONFIG_SYSTEM_CLE_DEBUGLEVEL > 0
|
|
static void cle_debug(FAR const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
/* Let vsyslog do the real work */
|
|
|
|
va_start(ap, fmt);
|
|
vsyslog(LOG_DEBUG, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: cle_write
|
|
*
|
|
* Description:
|
|
* Write a sequence of bytes to the console output device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_write(FAR struct cle_s *priv, FAR const char *buffer,
|
|
uint16_t buflen)
|
|
{
|
|
ssize_t nwritten;
|
|
uint16_t nremaining = buflen;
|
|
|
|
/* Loop until all bytes have been successfully written (or until a
|
|
* unrecoverable error is encountered)
|
|
*/
|
|
|
|
do
|
|
{
|
|
/* Put the next gulp */
|
|
|
|
nwritten = fwrite(buffer, sizeof(char), buflen, priv->outs);
|
|
|
|
/* Handle write errors. write() should neve return 0. */
|
|
|
|
if (nwritten <= 0)
|
|
{
|
|
/* EINTR is not really an error; it simply means that a signal was
|
|
* received while waiting for write.
|
|
*/
|
|
|
|
int errcode = errno;
|
|
if (nwritten == 0 || errcode != EINTR)
|
|
{
|
|
cledbg("ERROR: write to stdout failed: %d\n", errcode);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Decrement the count of bytes remaining to be sent (to handle the
|
|
* case of a partial write)
|
|
*/
|
|
|
|
else
|
|
{
|
|
nremaining -= nwritten;
|
|
}
|
|
}
|
|
while (nremaining > 0);
|
|
|
|
fflush(priv->outs);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_putch
|
|
*
|
|
* Description:
|
|
* Write a single character to the console output device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_putch(FAR struct cle_s *priv, char ch)
|
|
{
|
|
cle_write(priv, &ch, 1);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_getch
|
|
*
|
|
* Description:
|
|
* Get a single character from the console input device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cle_getch(FAR struct cle_s *priv)
|
|
{
|
|
char buffer;
|
|
ssize_t nread;
|
|
|
|
/* Loop until we successfully read a character (or until an unexpected
|
|
* error occurs).
|
|
*/
|
|
|
|
do
|
|
{
|
|
/* Read one character from the incoming stream */
|
|
|
|
nread = fread (&buffer, sizeof(char), 1, priv->ins);
|
|
|
|
/* Check for error or end-of-file. */
|
|
|
|
if (nread <= 0)
|
|
{
|
|
/* EINTR is not really an error; it simply means that a signal we
|
|
* received while waiting for input.
|
|
*/
|
|
|
|
int errcode = errno;
|
|
if (nread == 0 || errcode != EINTR)
|
|
{
|
|
cledbg("ERROR: read from stdin failed: %d\n", errcode);
|
|
return -EIO;
|
|
}
|
|
}
|
|
}
|
|
while (nread < 1);
|
|
|
|
/* On success, return the character that was read */
|
|
|
|
cleinfo("Returning: %c[%02x]\n", isprint(buffer) ? buffer : '.', buffer);
|
|
return buffer;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_cursoron
|
|
*
|
|
* Description:
|
|
* Turn on the cursor
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_cursoron(FAR struct cle_s *priv)
|
|
{
|
|
/* Send the VT100 CURSORON command */
|
|
|
|
cle_write(priv, g_cursoron, sizeof(g_cursoron));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_cursoroff
|
|
*
|
|
* Description:
|
|
* Turn off the cursor
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_cursoroff(FAR struct cle_s *priv)
|
|
{
|
|
/* Send the VT100 CURSOROFF command */
|
|
|
|
cle_write(priv, g_cursoroff, sizeof(g_cursoroff));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_setcursor
|
|
*
|
|
* Description:
|
|
* Move the current cursor position to position (row,col)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_setcursor(FAR struct cle_s *priv, uint16_t column)
|
|
{
|
|
char buffer[16];
|
|
int len;
|
|
|
|
cleinfo("row=%d column=%d offset=%d\n", priv->row, column, priv->coloffs);
|
|
|
|
/* Format the cursor position command. The origin is (1,1). */
|
|
|
|
len = snprintf(buffer, 16, g_fmtcursorpos,
|
|
priv->row, column + priv->coloffs);
|
|
|
|
/* Send the VT100 CURSORPOS command */
|
|
|
|
cle_write(priv, buffer, len);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_setcolor
|
|
*
|
|
* Description:
|
|
* Set foreground color
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SYSTEM_COLOR_CLE
|
|
static void cle_setcolor(FAR struct cle_s *priv, uint8_t color)
|
|
{
|
|
char buffer[16];
|
|
int len;
|
|
|
|
len = snprintf(buffer, 16, g_setcolor, color);
|
|
cle_write(priv, buffer, len);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: cle_outputprompt
|
|
*
|
|
* Description:
|
|
* Send the prompt to the screen
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_outputprompt(FAR struct cle_s *priv)
|
|
{
|
|
#ifdef CONFIG_SYSTEM_COLOR_CLE
|
|
cle_setcolor(priv, COLOR_PROMPT);
|
|
#endif
|
|
cle_write(priv, priv->prompt, strlen(priv->prompt));
|
|
#ifdef CONFIG_SYSTEM_COLOR_CLE
|
|
cle_setcolor(priv, COLOR_OUTPUT);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_clrscr
|
|
*
|
|
* Description:
|
|
* Clear the screen, and re-establish the prompt
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_clrscr(FAR struct cle_s *priv)
|
|
{
|
|
/* Send the VT100 CLEARSCREEN command */
|
|
|
|
cle_write(priv, g_clrscr, sizeof(g_clrscr));
|
|
cle_write(priv, g_home, sizeof(g_home));
|
|
priv->row = 1;
|
|
cle_outputprompt(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_getcursor
|
|
*
|
|
* Description:
|
|
* Get the current cursor position.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cle_getcursor(FAR struct cle_s *priv, FAR uint16_t *prow,
|
|
FAR uint16_t *pcolumn)
|
|
{
|
|
uint32_t row;
|
|
uint32_t column;
|
|
int nbad;
|
|
int ch;
|
|
|
|
/* Send the VT100 GETCURSOR command */
|
|
|
|
cle_write(priv, g_getcursor, sizeof(g_getcursor));
|
|
|
|
/* We expect to get back ESC[v;hR where v is the row and h is the column.
|
|
* once the sequence has started we don't expect any characters
|
|
* interspersed.
|
|
*/
|
|
|
|
for (nbad = 0; nbad < 10; nbad++)
|
|
{
|
|
/* Look for initial ESC */
|
|
|
|
ch = cle_getch(priv);
|
|
if (ch != ASCII_ESC)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* Have ESC, now we expect '[' */
|
|
|
|
ch = cle_getch(priv);
|
|
if (ch != '[')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* ...now we expect to see a numeric value terminated with ';' */
|
|
|
|
row = 0;
|
|
|
|
while (isdigit(ch = cle_getch(priv)))
|
|
{
|
|
row = row * 10 + (ch - '0');
|
|
}
|
|
|
|
if (ch != ';')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* ...now we expect to see another numeric value terminated with 'R' */
|
|
|
|
column = 0;
|
|
while (isdigit(ch = cle_getch(priv)))
|
|
{
|
|
column = 10 * column + (ch - '0');
|
|
}
|
|
|
|
/* ...we are done */
|
|
|
|
cleinfo("row=%" PRId32 " column=%" PRId32 "\n", row, column);
|
|
|
|
/* Make sure that the values are within range */
|
|
|
|
if (row <= UINT16_MAX && column <= UINT16_MAX)
|
|
{
|
|
*prow = row;
|
|
*pcolumn = column;
|
|
return OK;
|
|
}
|
|
else
|
|
{
|
|
return -ERANGE;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_clrtoeol
|
|
*
|
|
* Description:
|
|
* Clear the display from the current cursor position to the end of the
|
|
* current line.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_clrtoeol(FAR struct cle_s *priv)
|
|
{
|
|
/* Send the VT100 ERASETOEOL command */
|
|
|
|
cle_write(priv, g_erasetoeol, sizeof(g_erasetoeol));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_opentext
|
|
*
|
|
* Description:
|
|
* Make space for new text of size 'increment' at the specified cursor
|
|
* position. This function will not allow text grow beyond ('linelen' - 1)
|
|
* in size.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool cle_opentext(FAR struct cle_s *priv, uint16_t pos,
|
|
uint16_t increment)
|
|
{
|
|
int i;
|
|
|
|
cleinfo("pos=%ld increment=%ld\n", (long)pos, (long)increment);
|
|
|
|
/* Check if there is space in the line buffer to open up a region the size
|
|
* of 'increment'
|
|
*/
|
|
|
|
if (priv->nchars + increment >= priv->linelen)
|
|
{
|
|
CLE_BEL(priv);
|
|
return false;
|
|
}
|
|
|
|
/* Move text to make space for new text of size 'increment' at the current
|
|
* cursor position
|
|
*/
|
|
|
|
for (i = priv->nchars - 1; i >= pos; i--)
|
|
{
|
|
priv->line[i + increment] = priv->line[i];
|
|
}
|
|
|
|
/* Adjust end of file position */
|
|
|
|
priv->nchars += increment;
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_closetext
|
|
*
|
|
* Description:
|
|
* Delete a region in the line buffer by copying the end of the line buffer
|
|
* over the deleted region and adjusting the size of the region.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_closetext(FAR struct cle_s *priv, uint16_t pos,
|
|
uint16_t size)
|
|
{
|
|
int i;
|
|
|
|
cleinfo("pos=%ld size=%ld\n", (long)pos, (long)size);
|
|
|
|
/* Close up the gap to remove 'size' characters at 'pos' */
|
|
|
|
for (i = pos + size; i < priv->nchars; i++)
|
|
{
|
|
priv->line[i - size] = priv->line[i];
|
|
}
|
|
|
|
/* Adjust sizes and positions */
|
|
|
|
priv->nchars -= size;
|
|
|
|
if (priv->curpos > pos)
|
|
{
|
|
/* Check if the cursor position is beyond the deleted region */
|
|
|
|
if (priv->curpos - pos > size)
|
|
{
|
|
/* Yes... just subtract the size of the deleted region */
|
|
|
|
priv->curpos -= size;
|
|
}
|
|
else
|
|
{
|
|
/* What if the position is within the deleted region? Set it to
|
|
* the beginning of the deleted region.
|
|
*/
|
|
|
|
priv->curpos = pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_showtext
|
|
*
|
|
* Description:
|
|
* Update the display based on the last operation. This function is
|
|
* called at the beginning of the editor loop.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_showtext(FAR struct cle_s *priv)
|
|
{
|
|
uint16_t column;
|
|
uint16_t tabcol;
|
|
|
|
/* Turn off the cursor during the update. */
|
|
|
|
cle_cursoroff(priv);
|
|
|
|
/* Set the cursor position to the beginning of this row */
|
|
|
|
#ifdef CONFIG_SYSTEM_COLOR_CLE
|
|
cle_setcolor(priv, COLOR_COMMAND);
|
|
#endif
|
|
cle_setcursor(priv, 0);
|
|
cle_clrtoeol(priv);
|
|
|
|
/* Loop for each column */
|
|
|
|
for (column = 0; column < priv->nchars; )
|
|
{
|
|
/* Perform TAB expansion */
|
|
|
|
if (priv->line[column] == '\t')
|
|
{
|
|
tabcol = NEXT_TAB(column);
|
|
if (tabcol < priv->linelen)
|
|
{
|
|
for (; column < tabcol; column++)
|
|
{
|
|
cle_putch(priv, ' ');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Break out of the loop... there is nothing left on the
|
|
* line but whitespace.
|
|
*/
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Add the normal character to the display */
|
|
|
|
else
|
|
{
|
|
cle_putch(priv, priv->line[column]);
|
|
column++;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_SYSTEM_COLOR_CLE
|
|
cle_setcolor(priv, COLOR_OUTPUT);
|
|
#endif
|
|
|
|
/* Turn the cursor back on */
|
|
|
|
cle_cursoron(priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_insertch
|
|
*
|
|
* Description:
|
|
* Insert one character into the line buffer
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void cle_insertch(FAR struct cle_s *priv, char ch)
|
|
{
|
|
cleinfo("curpos=%" PRId16 " ch=%c[%02x]\n", priv->curpos,
|
|
isprint(ch) ? ch : '.', ch);
|
|
|
|
/* Make space in the buffer for the new character */
|
|
|
|
if (cle_opentext(priv, priv->curpos, 1))
|
|
{
|
|
/* Add the new character to the buffer */
|
|
|
|
priv->line[priv->curpos++] = ch;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: cle_editloop
|
|
*
|
|
* Description:
|
|
* Command line editor loop
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int cle_editloop(FAR struct cle_s *priv)
|
|
{
|
|
/* Loop while we are in command mode */
|
|
|
|
for (; ; )
|
|
{
|
|
#if 1 /* Perhaps here should be a config switch */
|
|
char state = 0;
|
|
#endif
|
|
int ch;
|
|
|
|
/* Make sure that the display reflects the current state */
|
|
|
|
cle_showtext(priv);
|
|
cle_setcursor(priv, priv->curpos);
|
|
|
|
/* 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
|
|
*/
|
|
|
|
/* 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);
|
|
if (ch < 0)
|
|
{
|
|
return -EIO;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* 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)
|
|
{
|
|
case KEY_BEGINLINE: /* Move cursor to start of current line */
|
|
{
|
|
priv->curpos = 0;
|
|
}
|
|
break;
|
|
|
|
case KEY_LEFT: /* Move the cursor left 1 character */
|
|
{
|
|
if (priv->curpos > 0)
|
|
{
|
|
priv->curpos--;
|
|
}
|
|
else
|
|
{
|
|
CLE_BEL(priv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_DEL: /* Delete 1 character at the cursor */
|
|
{
|
|
if (priv->curpos < priv->nchars)
|
|
{
|
|
cle_closetext(priv, priv->curpos, 1);
|
|
}
|
|
else
|
|
{
|
|
CLE_BEL(priv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_ENDLINE: /* Move cursor to end of current line */
|
|
{
|
|
priv->curpos = priv->nchars;
|
|
}
|
|
break;
|
|
|
|
case KEY_RIGHT: /* Move the cursor right one character */
|
|
{
|
|
if (priv->curpos < priv->nchars)
|
|
{
|
|
priv->curpos++;
|
|
}
|
|
else
|
|
{
|
|
CLE_BEL(priv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ASCII_DEL:
|
|
case KEY_DELLEFT: /* Delete 1 character before the cursor */
|
|
{
|
|
if (priv->curpos > 0)
|
|
{
|
|
cle_closetext(priv, --priv->curpos, 1);
|
|
}
|
|
else
|
|
{
|
|
CLE_BEL(priv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_DELEOL: /* Delete to the end of the line */
|
|
{
|
|
priv->nchars = (priv->nchars > 0 ? priv->curpos : 0);
|
|
}
|
|
break;
|
|
|
|
case KEY_DELLINE: /* Delete to the end of the line */
|
|
{
|
|
priv->nchars = 0;
|
|
priv->curpos = 0;
|
|
}
|
|
break;
|
|
|
|
case KEY_CLRSCR: /* Clear the screen & return cursor to top */
|
|
cle_clrscr(priv);
|
|
break;
|
|
|
|
case KEY_QUOTE: /* Quoted character follows */
|
|
{
|
|
ch = cle_getch(priv);
|
|
if (ch < 0)
|
|
{
|
|
return -EIO;
|
|
}
|
|
|
|
/* Insert the next character unconditionally */
|
|
|
|
cle_insertch(priv, ch);
|
|
}
|
|
break;
|
|
|
|
/* Newline terminates editing. But what is a newline? */
|
|
|
|
#if defined(CONFIG_EOL_IS_CR) || defined(CONFIG_EOL_IS_EITHER_CRLF)
|
|
case '\r': /* CR terminates line */
|
|
|
|
#elif defined(CONFIG_EOL_IS_LF) || defined(CONFIG_EOL_IS_BOTH_CRLF) || \
|
|
defined(CONFIG_EOL_IS_EITHER_CRLF)
|
|
|
|
case '\n': /* LF terminates line */
|
|
#endif
|
|
{
|
|
/* Add the newline to the buffer at the end of the line */
|
|
|
|
priv->curpos = priv->nchars;
|
|
cle_insertch(priv, '\n');
|
|
cle_putch(priv, '\n');
|
|
return OK;
|
|
}
|
|
break;
|
|
|
|
#if defined(CONFIG_EOL_IS_BOTH_CRLF)
|
|
case '\r': /* Wait for the LF */
|
|
break;
|
|
#endif
|
|
|
|
/* Text to insert or unimplemented/invalid keypresses */
|
|
|
|
default:
|
|
{
|
|
/* Ignore all control characters except for tab and newline */
|
|
|
|
if (!iscntrl(ch) || ch == '\t')
|
|
{
|
|
/* Insert the filtered character into the buffer */
|
|
|
|
cle_insertch(priv, ch);
|
|
}
|
|
else
|
|
{
|
|
CLE_BEL(priv);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Command line processing
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: cle
|
|
*
|
|
* Description:
|
|
* EMACS-like command line editor. This is actually more like readline
|
|
* than is the NuttX readline!
|
|
*
|
|
****************************************************************************/
|
|
|
|
int cle(FAR char *line, const char *prompt, uint16_t linelen,
|
|
FILE *instream, FILE *outstream)
|
|
{
|
|
FAR struct cle_s priv;
|
|
uint16_t column;
|
|
int ret;
|
|
|
|
/* Initialize the CLE state structure */
|
|
|
|
memset(&priv, 0, sizeof(struct cle_s));
|
|
|
|
priv.linelen = linelen;
|
|
priv.line = line;
|
|
|
|
priv.ins = instream;
|
|
priv.outs = outstream;
|
|
|
|
/* Store the prompt in case we need to re-print it */
|
|
|
|
priv.prompt = prompt;
|
|
cle_outputprompt(&priv);
|
|
|
|
/* Get the current cursor position */
|
|
|
|
ret = cle_getcursor(&priv, &priv.row, &column);
|
|
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Turn the column number into an offset */
|
|
|
|
if (column < 1)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv.coloffs = column;
|
|
|
|
cleinfo("row=%d column=%d\n", priv.row, column);
|
|
|
|
/* The editor loop */
|
|
|
|
ret = cle_editloop(&priv);
|
|
|
|
/* Make sure that the line is NUL terminated */
|
|
|
|
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;
|
|
}
|