diff options
author | Gregory Nutt <gnutt@nuttx.org> | 2014-02-02 10:25:53 -0600 |
---|---|---|
committer | Gregory Nutt <gnutt@nuttx.org> | 2014-02-02 10:25:53 -0600 |
commit | c9b74c175ad4502245cace41464aba69f8a1eaec (patch) | |
tree | 7e2b4789194c6b3e6865e24aee5bac6a7c0fe484 /apps | |
parent | e364cc0f190f65ca37a08fbba3daa577d23cf51b (diff) | |
download | nuttx-c9b74c175ad4502245cace41464aba69f8a1eaec.tar.gz nuttx-c9b74c175ad4502245cace41464aba69f8a1eaec.tar.bz2 nuttx-c9b74c175ad4502245cace41464aba69f8a1eaec.zip |
Add an EMACS-like command line editor that can be used wit NSH
Diffstat (limited to 'apps')
-rw-r--r-- | apps/ChangeLog.txt | 7 | ||||
-rw-r--r-- | apps/include/cle.h | 87 | ||||
-rw-r--r-- | apps/nshlib/Kconfig | 30 | ||||
-rw-r--r-- | apps/nshlib/nsh_session.c | 13 | ||||
-rw-r--r-- | apps/nshlib/nsh_stdsession.c | 13 | ||||
-rw-r--r-- | apps/system/Kconfig | 4 | ||||
-rw-r--r-- | apps/system/Make.defs | 4 | ||||
-rw-r--r-- | apps/system/Makefile | 2 | ||||
-rw-r--r-- | apps/system/cle/.gitignore | 11 | ||||
-rw-r--r-- | apps/system/cle/Kconfig | 37 | ||||
-rw-r--r-- | apps/system/cle/Makefile | 103 | ||||
-rw-r--r-- | apps/system/cle/cle.c | 929 |
12 files changed, 1232 insertions, 8 deletions
diff --git a/apps/ChangeLog.txt b/apps/ChangeLog.txt index 146d0943b..a9450b5d3 100644 --- a/apps/ChangeLog.txt +++ b/apps/ChangeLog.txt @@ -811,3 +811,10 @@ untested conditions (2014-1-21). 6.34 2014-xx-xx Gregory Nutt <gnutt@nuttx.org> + + * apps/system/cle: Add a EMACS-like command line editor. This CLE, + is really more like readline than the NuttX readline is! Not fully + fully on initial checkout (2014-02-02). + * apps/nshlib: Use of the standard tiney readline (about .25KB) is now + an option and can be replaces with the EMACX-like CLE (about 2KB) + (2014-02-02). diff --git a/apps/include/cle.h b/apps/include/cle.h new file mode 100644 index 000000000..5cb903212 --- /dev/null +++ b/apps/include/cle.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * apps/include/cle.h + * + * Copyright (C) 2014 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. + * + ****************************************************************************/ + +#ifndef __APPS_INCLUDE_CLE_H +#define __APPS_INCLUDE_CLE_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <stdint.h> +#include <stdio.h> + +#ifdef CONFIG_SYSTEM_CLE + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: cle + * + * Description: + * EMACS-like command line editor. This is actually more like readline + * than is the NuttX readline! + * + ****************************************************************************/ + +int cle(FAR char *line, uint16_t linelen, FILE *instream, FILE *outstream); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_SYSTEM_CLE */ +#endif /* __APPS_INCLUDE_CLE_H */ diff --git a/apps/nshlib/Kconfig b/apps/nshlib/Kconfig index 7fb6a52b3..8a9d1e900 100644 --- a/apps/nshlib/Kconfig +++ b/apps/nshlib/Kconfig @@ -6,13 +6,37 @@ config NSH_LIBRARY bool "NSH Library" default n - select SYSTEM_READLINE ---help--- - Build the NSH support library. This is used, for example, by examples/nsh - in order to implement the full NuttShell (NSH). + Build the NSH support library. This is used, for example, by + examples/nsh in order to implement the full NuttShell (NSH). if NSH_LIBRARY +choice + prompt "Command Line Editor" + default NSH_READLINE + +config NSH_READLINE + bool "Minimal readline()" + select SYSTEM_READLINE + ---help--- + Selects the minimal implementation of readline(). This minimal + implementation provides on backspace for command line editing. + +config NSH_CLE + bool "Command Line Editor" + select SYSTEM_CLE + ---help--- + Selects the more extensive, EMACS-like command line editor. + Select this option only if (1) you don't mind a modest increase + in the FLASH footprint, and (2) you work with a terminal that + support VT100 editing commands. + + Selecting this option will add probably 1.5-2KB to the FLASH + footprint. + +endchoice + config NSH_BUILTIN_APPS bool "Enable built-in applications" default n diff --git a/apps/nshlib/nsh_session.c b/apps/nshlib/nsh_session.c index 2272b1e30..de6710bfe 100644 --- a/apps/nshlib/nsh_session.c +++ b/apps/nshlib/nsh_session.c @@ -1,7 +1,7 @@ /**************************************************************************** * apps/nshlib/nsh_session.c * - * Copyright (C) 2007-2009, 2011-2013 Gregory Nutt. All rights reserved. + * Copyright (C) 2007-2009, 2011-2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt <gnutt@nuttx.org> * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,11 @@ #include <stdio.h> #include <stdlib.h> -#include <apps/readline.h> +#ifdef CONFIG_NSH_CLE +# include <apps/cle.h> +#else +# include <apps/readline.h> +#endif #include "nsh.h" #include "nsh_console.h" @@ -133,8 +137,13 @@ int nsh_session(FAR struct console_stdio_s *pstate) * or any read failure. */ +#ifdef CONFIG_NSH_CLE + ret = cle(pstate->cn_line, CONFIG_NSH_LINELEN, + INSTREAM(pstate), OUTSTREAM(pstate)); +#else ret = readline(pstate->cn_line, CONFIG_NSH_LINELEN, INSTREAM(pstate), OUTSTREAM(pstate)); +#endif if (ret != EOF) { /* Parse process the command */ diff --git a/apps/nshlib/nsh_stdsession.c b/apps/nshlib/nsh_stdsession.c index 11fae7db7..07def5b3b 100644 --- a/apps/nshlib/nsh_stdsession.c +++ b/apps/nshlib/nsh_stdsession.c @@ -1,7 +1,7 @@ /**************************************************************************** * apps/nshlib/nsh_stdsession.c * - * Copyright (C) 2013 Gregory Nutt. All rights reserved. + * Copyright (C) 2013-2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt <gnutt@nuttx.org> * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,11 @@ #include <stdio.h> #include <stdlib.h> -#include <apps/readline.h> +#ifdef CONFIG_NSH_CLE +# include <apps/cle.h> +#else +# include <apps/readline.h> +#endif #include "nsh.h" #include "nsh_console.h" @@ -124,7 +128,12 @@ int nsh_session(FAR struct console_stdio_s *pstate) * or any read failure. */ +#ifdef CONFIG_NSH_CLE + ret = cle(pstate->cn_line, CONFIG_NSH_LINELEN, + stdin, stdout); +#else ret = std_readline(pstate->cn_line, CONFIG_NSH_LINELEN); +#endif if (ret != EOF) { /* Parse process the command */ diff --git a/apps/system/Kconfig b/apps/system/Kconfig index 15836f1f2..e7d00bc2e 100644 --- a/apps/system/Kconfig +++ b/apps/system/Kconfig @@ -63,6 +63,10 @@ menu "USB Monitor" source "$APPSDIR/system/usbmonitor/Kconfig" endmenu +menu "EMACS-like Command Line Editor" +source "$APPSDIR/system/cle/Kconfig" +endmenu + menu "VI Work-Alike Editor" source "$APPSDIR/system/vi/Kconfig" endmenu diff --git a/apps/system/Make.defs b/apps/system/Make.defs index c0af2ec15..33a40c0aa 100644 --- a/apps/system/Make.defs +++ b/apps/system/Make.defs @@ -102,6 +102,10 @@ ifeq ($(CONFIG_SYSTEM_USBMSC),y) CONFIGURED_APPS += system/usbmsc endif +ifeq ($(CONFIG_SYSTEM_CLE),y) +CONFIGURED_APPS += system/cle +endif + ifeq ($(CONFIG_SYSTEM_VI),y) CONFIGURED_APPS += system/vi endif diff --git a/apps/system/Makefile b/apps/system/Makefile index 08a67d22d..149c7a4ab 100644 --- a/apps/system/Makefile +++ b/apps/system/Makefile @@ -37,7 +37,7 @@ # Sub-directories containing system task -SUBDIRS = cdcacm composite flash_eraseall free i2c inifile install +SUBDIRS = cdcacm cle composite flash_eraseall free i2c inifile install SUBDIRS += nxplayer poweroff ramtest ramtron readline sdcard stackmonitor SUBDIRS += sysinfo usbmonitor usbmsc vi zmodem diff --git a/apps/system/cle/.gitignore b/apps/system/cle/.gitignore new file mode 100644 index 000000000..83bd7b811 --- /dev/null +++ b/apps/system/cle/.gitignore @@ -0,0 +1,11 @@ +/Make.dep +/.depend +/.built +/*.asm +/*.rel +/*.lst +/*.sym +/*.adb +/*.lib +/*.src +/*.obj diff --git a/apps/system/cle/Kconfig b/apps/system/cle/Kconfig new file mode 100644 index 000000000..d846a0cf8 --- /dev/null +++ b/apps/system/cle/Kconfig @@ -0,0 +1,37 @@ +# +# For a description of the syntax of this configuration file, +# see misc/tools/kconfig-language.txt. +# + +config SYSTEM_CLE + bool "Tiny VI work-alike command line editor" + default n + ---help--- + Enable support for NuttX tiny EMACS-like command line editor. + + Omitted features: + - No keypad cursor control support + - No word oriented operations. + + Assumptions and Limitations: + - A VT100 host terminal is assumed. + - A fixed width character set (like Courier) is assumed + + Memory Usage: Looks like 1.5-2KB + +if SYSTEM_CLE + +config SYSTEM_CLE_DEBUGLEVEL + int "Debug level" + default 0 + range 0 2 + ---help--- + 0=Debug off; 1=Print errors on console; 2=Print debug information + on the console. + + Debug output is generated with syslog. The editor works on + /dev/console. In order to get both a usable display and also + readable debug output, syslog'ing should sent to some device other + than /dev/console (which is the default). + +endif # SYSTEM_CLE diff --git a/apps/system/cle/Makefile b/apps/system/cle/Makefile new file mode 100644 index 000000000..cce4bb109 --- /dev/null +++ b/apps/system/cle/Makefile @@ -0,0 +1,103 @@ +############################################################################ +# apps/system/cle/Makefile +# +# Copyright (C) 2014 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. +# +############################################################################ + +-include $(TOPDIR)/.config +-include $(TOPDIR)/Make.defs +include $(APPDIR)/Make.defs + +ifeq ($(WINTOOL),y) +INCDIROPT = -w +endif + +# EMACS-like Command Line Editor (CLE) + +ASRCS = +CSRCS = cle.c + +AOBJS = $(ASRCS:.S=$(OBJEXT)) +COBJS = $(CSRCS:.c=$(OBJEXT)) + +SRCS = $(ASRCS) $(CSRCS) +OBJS = $(AOBJS) $(COBJS) + +ifeq ($(CONFIG_WINDOWS_NATIVE),y) + BIN = ..\..\libapps$(LIBEXT) +else +ifeq ($(WINTOOL),y) + BIN = ..\\..\\libapps$(LIBEXT) +else + BIN = ../../libapps$(LIBEXT) +endif +endif + +ROOTDEPPATH = --dep-path . + +# Common build + +VPATH = + +all: .built +.PHONY: context depend clean distclean + +$(AOBJS): %$(OBJEXT): %.S + $(call ASSEMBLE, $<, $@) + +$(COBJS): %$(OBJEXT): %.c + $(call COMPILE, $<, $@) + +.built: $(OBJS) + $(call ARCHIVE, $(BIN), $(OBJS)) + $(Q) touch .built + +context: + +# Create dependencies + +.depend: Makefile $(SRCS) + $(Q) $(MKDEP) $(ROOTDEPPATH) "$(CC)" -- $(CFLAGS) -- $(SRCS) >Make.dep + $(Q) touch $@ + +depend: .depend + +clean: + $(call DELFILE, .built) + $(call CLEAN) + +distclean: clean + $(call DELFILE, Make.dep) + $(call DELFILE, .depend) + +-include Make.dep + diff --git a/apps/system/cle/cle.c b/apps/system/cle/cle.c new file mode 100644 index 000000000..6ed8d12aa --- /dev/null +++ b/apps/system/cle/cle.c @@ -0,0 +1,929 @@ +/**************************************************************************** + * apps/system/cle/cle.c + * + * Copyright (C) 2014 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 <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 <apps/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(cle) cle_putch(cle,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_SYSEM_CLE_DEBUGLEVEL +# define CONFIG_SYSEM_CLE_DEBUGLEVEL 0 +#endif + +#ifdef CONFIG_CPP_HAVE_VARARGS +# if CONFIG_SYSEM_CLE_DEBUGLEVEL > 0 +# define vidbg(format, arg...) \ + syslog(EXTRA_FMT format EXTRA_ARG, ##arg) +# define vvidbg(format, ap) \ + vsyslog(format, ap) +# else +# define vidbg(x...) +# define vvidbg(x...) +# endif + +# if CONFIG_SYSEM_CLE_DEBUGLEVEL > 1 +# define vivdbg(format, arg...) \ + syslog(EXTRA_FMT format EXTRA_ARG, ##arg) +# else +# define vivdbg(x...) +# endif +#else +# if CONFIG_SYSEM_CLE_DEBUGLEVEL > 0 +# define vidbg syslog +# define vvidbg vsyslog +# else +# define vidbg (void) +# define vvidbg (void) +# endif + +# if CONFIG_SYSEM_CLE_DEBUGLEVEL > 1 +# define vivdbg syslog +# else +# define vivdbg (void) +# endif +#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_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 iun */ + uint16_t coloffs; /* Left cursor offset */ + uint16_t linelen; /* Size of the line buffer */ + uint16_t nchars; /* Size of data in the line buffer */ + int infd; /* Input file descriptor */ + int outfd; /* Output file descriptor */ + FAR char *line; /* Line buffer */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Low-level display and data entry functions */ + +static void cle_write(FAR struct cle_s *cle, FAR const char *buffer, + uint16_t buflen); +static void cle_putch(FAR struct cle_s *cle, char ch); +static int cle_getch(FAR struct cle_s *cle); +static void cle_cursoron(FAR struct cle_s *cle); +static void cle_cursoroff(FAR struct cle_s *cle); +static void cle_setcursor(FAR struct cle_s *cle, uint16_t column); +static int cle_getcursor(FAR struct cle_s *cle, uint16_t *prow, + uint16_t *pcolumn); +static void cle_clrtoeol(FAR struct cle_s *cle); + +/* Editor function */ + +static bool cle_opentext(FAR struct cle_s *cle, uint16_t pos, + uint16_t increment); +static void cle_closetext(FAR struct cle_s *cle, uint16_t pos, uint16_t size); +static void cle_showtext(FAR struct cle_s *cle); +static void cle_insertch(FAR struct cle_s *cle, char ch); +static int cle_editloop(FAR struct cle_s *cle); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* 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_attriboff[] = VT100_MODESOFF; +static const char g_fmtcursorpos[] = VT100_FMT_CURSORPOS; + +/* Error format strings */ + +static const char g_fmtnotvalid[] = "Command not valid"; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Low-level display and data entry functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cle_write + * + * Description: + * Write a sequence of bytes to the console device (stdout, fd = 1). + * + ****************************************************************************/ + +static void cle_write(FAR struct cle_s *cle, FAR const char *buffer, + uint16_t buflen) +{ + ssize_t nwritten; + uint16_t nremaining = buflen; + + //vivdbg("buffer=%p buflen=%d\n", buffer, (int)buflen); + + /* Loop until all bytes have been successuflly written (or until a + * un-recoverable error is encountered) + */ + + do + { + /* Take the next gulp */ + + nwritten = write(cle->outfd, buffer, buflen); + + /* 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) + { + vidbg("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); +} + +/**************************************************************************** + * Name: cle_putch + * + * Description: + * Write a single character to the console device. + * + ****************************************************************************/ + +static void cle_putch(FAR struct cle_s *cle, char ch) +{ + cle_write(cle, &ch, 1); +} + +/**************************************************************************** + * Name: cle_getch + * + * Description: + * Get a single character from the console device (stdin, fd = 0). + * + ****************************************************************************/ + +static int cle_getch(FAR struct cle_s *cle) +{ + 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 = read(cle->infd, &buffer, 1); + + /* 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) + { + vidbg("ERROR: read from stdin failed: %d\n", errcode); + return -EIO; + } + } + } + while (nread < 1); + + /* On success, return the character that was read */ + + vivdbg("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 *cle) +{ + /* Send the VT100 CURSORON command */ + + cle_write(cle, g_cursoron, sizeof(g_cursoron)); +} + +/**************************************************************************** + * Name: cle_cursoroff + * + * Description: + * Turn off the cursor + * + ****************************************************************************/ + +static void cle_cursoroff(FAR struct cle_s *cle) +{ + /* Send the VT100 CURSOROFF command */ + + cle_write(cle, 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 *cle, uint16_t column) +{ + char buffer[16]; + int len; + + vivdbg("row=%d column=%d\n", row, column); + + /* Format the cursor position command. The origin is (1,1). */ + + len = snprintf(buffer, 16, g_fmtcursorpos, cle->row, column + cle->coloffs); + + /* Send the VT100 CURSORPOS command */ + + cle_write(cle, buffer, len); +} + +/**************************************************************************** + * Name: cle_getcursor + * + * Description: + * Get the current cursor position. + * + ****************************************************************************/ + +static int cle_getcursor(FAR struct cle_s *cle, 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(cle, g_getcursor, sizeof(g_getcursor)); + + /* We expect to get ESCv;hR where v is the row and h is the column */ + + nbad = 0; + for (;;) + { + /* Get the next character from the input */ + + ch = cle_getch(cle); + if (ch == ASCII_ESC) + { + break; + } + else if (ch < 0) + { + return -EIO; + } + else if (++nbad > 3) + { + /* We are probably talking to a non-VT100 terminal! */ + + return -EINVAL; + } + } + + /* Now we expect to see a numeric value terminated with ';' */ + + row = 0; + for (;;) + { + /* Get the next character from the input */ + + ch = cle_getch(cle); + if (isdigit(ch)) + { + row = 10*row + (ch - '0'); + } + else if (ch == ';') + { + break; + } + else if (ch < 0) + { + return -EIO; + } + else + { + return -EINVAL; + } + } + + /* Now we expect to see another numeric value terminated with 'R' */ + + column = 0; + for (;;) + { + /* Get the next character from the input */ + + ch = cle_getch(cle); + if (isdigit(ch)) + { + column = 10*column + (ch - '0'); + } + else if (ch == 'R') + { + break; + } + else if (ch < 0) + { + return -EIO; + } + else + { + return -EINVAL; + } + } + + vivdbg("row=%ld column=%ld\n", row, column); + + /* Make sure that the values are within range */ + + if (row <= UINT16_MAX && column <= UINT16_MAX) + { + *prow = row; + *pcolumn = column; + return OK; + } + + return -ERANGE; +} + +/**************************************************************************** + * 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 *cle) +{ + /* Send the VT100 ERASETOEOL command */ + + cle_write(cle, g_erasetoeol, sizeof(g_erasetoeol)); +} + +/**************************************************************************** + * Name: cle_opentext + * + * Description: + * Make space for new text of size 'increment' at the specified cursor + * position. + * + ****************************************************************************/ + +static bool cle_opentext(FAR struct cle_s *cle, uint16_t pos, uint16_t increment) +{ + int i; + + vivdbg("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 (cle->nchars + increment > cle->linelen) + { + return false; + } + + /* Move text to make space for new text of size 'increment' at the current + * cursor position + */ + + for (i = cle->nchars - 1; i >= pos; i--) + { + cle->line[i + increment] = cle->line[i]; + } + + /* Adjust end of file position */ + + cle->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 *cle, uint16_t pos, uint16_t size) +{ + int i; + + vivdbg("pos=%ld size=%ld\n", (long)pos, (long)size); + + /* Close up the gap to remove 'size' characters at 'pos' */ + + for (i = pos + size; i < cle->nchars; i++) + { + cle->line[i - size] = cle->line[i]; + } + + /* Adjust sizes and positions */ + + cle->nchars -= size; + + /* Check if the cursor position is beyond the deleted region */ + + if (cle->curpos > pos + size) + { + /* Yes... just subtract the size of the deleted region */ + + cle->curpos -= size; + } + + /* What if the position is within the deleted region? Set it to the + * beginning of the deleted region. + */ + + else if (cle->curpos > pos) + { + cle->curpos = pos; + } +} + +/**************************************************************************** + * Name: cle_showtext + * + * Description: + * Update the display based on the last operation. This function is + * called at the beginning of the processing loop in Command and Insert + * modes (and also in the continuous replace mode). + * + ****************************************************************************/ + +static void cle_showtext(FAR struct cle_s *cle) +{ + uint16_t column; + uint16_t tabcol; + + /* Turn off the cursor during the update. */ + + cle_cursoroff(cle); + + /* Set the cursor position to the beginning of this row */ + + cle_setcursor(cle, 0); + cle_clrtoeol(cle); + + /* Loop for each column */ + + for (column = 0; column < cle->nchars && column < cle->linelen; column++) + { + /* Perform TAB expansion */ + + if (cle->line[column] == '\t') + { + tabcol = NEXT_TAB(column); + if (tabcol < cle->linelen) + { + for (; column < tabcol; column++) + { + cle_putch(cle, ' '); + } + } + 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(cle, cle->line[column]); + column++; + } + } + + /* Turn the cursor back on */ + + cle_cursoron(cle); +} + +/**************************************************************************** + * Name: cle_insertch + * + * Description: + * Insert one character into the line buffer + * + ****************************************************************************/ + +static void cle_insertch(FAR struct cle_s *cle, char ch) +{ + vivdbg("curpos=%ld ch=%c[%02x]\n", cle->curpos, isprint(ch) ? ch : '.', ch); + + /* Make space in the buffer for the new character */ + + if (cle_opentext(cle, cle->curpos, 1)) + { + /* Add the new character to the buffer */ + + cle->line[cle->curpos++] = ch; + } +} + +/**************************************************************************** + * Name: cle_editloop + * + * Description: + * Command line editor loop + * + ****************************************************************************/ + +static int cle_editloop(FAR struct cle_s *cle) +{ + /* Loop while we are in command mode */ + + for (;;) + { + int ch; + + /* Make sure that the display reflects the current state */ + + cle_showtext(cle); + cle_setcursor(cle, cle->curpos); + + /* Get the next character from the input */ + + ch = cle_getch(cle); + if (ch < 0) + { + return -EIO; + } + + /* Then handle the character. */ + + switch (ch) + { + case KEY_BEGINLINE: /* Move cursor to start of current line */ + { + cle->curpos = 0; + } + break; + + case KEY_LEFT: /* Move the cursor left 1 character */ + { + if (cle->curpos > 0) + { + cle->curpos--; + } + else + { + CLE_BEL(cle); + } + } + break; + + case KEY_DEL: /* Delete 1 character at the cursor */ + case ASCII_DEL: + { + if (cle->curpos < cle->nchars) + { + cle_closetext(cle, cle->curpos, 1); + } + else + { + CLE_BEL(cle); + } + } + break; + + case KEY_ENDLINE: /* Move cursor to end of current line */ + { + if (cle->nchars > 0) + { + cle->curpos = cle->nchars - 1; + } + else + { + CLE_BEL(cle); + } + } + break; + + case KEY_RIGHT: /* Move the cursor right one character */ + { + if (cle->curpos < cle->nchars) + { + cle->curpos++; + } + else + { + CLE_BEL(cle); + } + } + break; + + case KEY_DELLEFT: /* Delete 1 character before the cursor */ + //case ASCII_BS: + { + if (cle->curpos > 0) + { + cle_closetext(cle, --cle->curpos, 1); + } + else + { + CLE_BEL(cle); + } + } + break; + + case KEY_DELEOL: /* Delete to the end of the line */ + { + cle->nchars = (cle->nchars > 0 ? cle->curpos + 1 : 0); + } + break; + + case KEY_DELLINE: /* Delete to the end of the line */ + { + cle->nchars = 0; + cle->curpos = 0; + } + break; + + case KEY_QUOTE: /* Quoted character follows */ + { + ch = cle_getch(cle); + if (ch < 0) + { + return -EIO; + } + + /* Insert the next character unconditionally */ + + cle_insertch(cle, ch); + } + break; + + /* Newline terminates editing */ + +#if defined(CONFIG_EOL_IS_CR) + case '\r': /* CR terminates line */ + { + return OK; + } + break; + +#elif defined(CONFIG_EOL_IS_BOTH_CRLF) + case '\r': /* Wait for the LF */ + break; +#endif + +#if defined(CONFIG_EOL_IS_LF) || defined(CONFIG_EOL_IS_BOTH_CRLF) + case '\n': /* LF terminates line */ + { + return OK; + } + break; +#endif + +#ifdef CONFIG_EOL_IS_EITHER_CRLF + case '\r': /* Ether CR or LF terminates line */ + case '\n': + { + return OK; + } + 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(cle, ch); + } + else + { + CLE_BEL(cle); + } + } + 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, uint16_t linelen, FILE *instream, FILE *outstream) +{ + FAR struct cle_s cle; + uint16_t column; + int ret; + + /* Initialize the cle state structure */ + + cle.linelen = linelen; + cle.line = line; + + /* REVISIT: Non-standard, non-portable */ + + cle.infd = instream->fs_fd; + cle.outfd = outstream->fs_fd; + + /* Get the current cursor position */ + + ret = cle_getcursor(&cle, &cle.row, &column); + if (ret < 0) + { + return ret; + } + + /* Turn the column number into an offset */ + + if (column < 1) + { + return -EINVAL; + } + + cle.coloffs = column - 1; + + /* The editor loop */ + + return cle_editloop(&cle); +} |