GithubHelp home page GithubHelp logo

antirez / linenoise Goto Github PK

View Code? Open in Web Editor NEW
3.7K 106.0 662.0 146 KB

A small self-contained alternative to readline and libedit

License: BSD 2-Clause "Simplified" License

Makefile 0.34% C 99.66%

linenoise's Introduction

Linenoise

A minimal, zero-config, BSD licensed, readline replacement used in Redis, MongoDB, Android and many other projects.

  • Single and multi line editing mode with the usual key bindings implemented.
  • History handling.
  • Completion.
  • Hints (suggestions at the right of the prompt as you type).
  • Multiplexing mode, with prompt hiding/restoring for asynchronous output.
  • About ~850 lines (comments and spaces excluded) of BSD license source code.
  • Only uses a subset of VT100 escapes (ANSI.SYS compatible).

Can a line editing library be 20k lines of code?

Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing?

So what usually happens is either:

  • Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (real world example of this problem: Tclsh).
  • Smaller programs not using a configure script not supporting line editing at all (A problem we had with redis-cli, for instance).

The result is a pollution of binaries without line editing support.

So I spent more or less two hours doing a reality check resulting in this little library: is it really needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.

Terminals, in 2010.

Apparently almost every terminal you can happen to use today has some kind of support for basic VT100 escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it, and now can work even on ANSI.SYS compatible terminals, since no VT220 specific sequences are used anymore.

The library is currently about 850 lines of code. In order to use it in your project just look at the example.c file in the source distribution, it is pretty straightforward. The library supports both a blocking mode and a multiplexing mode, see the API documentation later in this file for more information.

Linenoise is BSD-licensed code, so you can use both in free software and commercial software.

Tested with...

  • Linux text only console ($TERM = linux)
  • Linux KDE terminal application ($TERM = xterm)
  • Linux xterm ($TERM = xterm)
  • Linux Buildroot ($TERM = vt100)
  • Mac OS X iTerm ($TERM = xterm)
  • Mac OS X default Terminal.app ($TERM = xterm)
  • OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
  • IBM AIX 6.1
  • FreeBSD xterm ($TERM = xterm)
  • ANSI.SYS
  • Emacs comint mode ($TERM = dumb)

Please test it everywhere you can and report back!

Let's push this forward!

Patches should be provided in the respect of Linenoise sensibility for small easy to understand code.

Send feedbacks to antirez at gmail

The API

Linenoise is very easy to use, and reading the example shipped with the library should get you up to speed ASAP. Here is a list of API calls and how to use them. Let's start with the simple blocking mode:

char *linenoise(const char *prompt);

This is the main Linenoise call: it shows the user a prompt with line editing and history capabilities. The prompt you specify is used as a prompt, that is, it will be printed to the left of the cursor. The library returns a buffer with the line composed by the user, or NULL on end of file or when there is an out of memory condition.

When a tty is detected (the user is actually typing into a terminal session) the maximum editable line length is LINENOISE_MAX_LINE. When instead the standard input is not a tty, which happens every time you redirect a file to a program, or use it in an Unix pipeline, there are no limits to the length of the line that can be returned.

The returned line should be freed with the free() standard system call. However sometimes it could happen that your program uses a different dynamic allocation library, so you may also used linenoiseFree to make sure the line is freed with the same allocator it was created.

The canonical loop used by a program using Linenoise will be something like this:

while((line = linenoise("hello> ")) != NULL) {
    printf("You wrote: %s\n", line);
    linenoiseFree(line); /* Or just free(line) if you use libc malloc. */
}

Single line VS multi line editing

By default, Linenoise uses single line editing, that is, a single row on the screen will be used, and as the user types more, the text will scroll towards left to make room. This works if your program is one where the user is unlikely to write a lot of text, otherwise multi line editing, where multiple screens rows are used, can be a lot more comfortable.

In order to enable multi line editing use the following API call:

linenoiseSetMultiLine(1);

You can disable it using 0 as argument.

History

Linenoise supporst history, so that the user does not have to retype again and again the same things, but can use the down and up arrows in order to search and re-edit already inserted lines of text.

The followings are the history API calls:

int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);

Use linenoiseHistoryAdd every time you want to add a new element to the top of the history (it will be the first the user will see when using the up arrow).

Note that for history to work, you have to set a length for the history (which is zero by default, so history will be disabled if you don't set a proper one). This is accomplished using the linenoiseHistorySetMaxLen function.

Linenoise has direct support for persisting the history into an history file. The functions linenoiseHistorySave and linenoiseHistoryLoad do just that. Both functions return -1 on error and 0 on success.

Mask mode

Sometimes it is useful to allow the user to type passwords or other secrets that should not be displayed. For such situations linenoise supports a "mask mode" that will just replace the characters the user is typing with * characters, like in the following example:

$ ./linenoise_example
hello> get mykey
echo: 'get mykey'
hello> /mask
hello> *********

You can enable and disable mask mode using the following two functions:

void linenoiseMaskModeEnable(void);
void linenoiseMaskModeDisable(void);

Completion

Linenoise supports completion, which is the ability to complete the user input when she or he presses the <TAB> key.

In order to use completion, you need to register a completion callback, which is called every time the user presses <TAB>. Your callback will return a list of items that are completions for the current string.

The following is an example of registering a completion callback:

linenoiseSetCompletionCallback(completion);

The completion must be a function returning void and getting as input a const char pointer, which is the line the user has typed so far, and a linenoiseCompletions object pointer, which is used as argument of linenoiseAddCompletion in order to add completions inside the callback. An example will make it more clear:

void completion(const char *buf, linenoiseCompletions *lc) {
    if (buf[0] == 'h') {
        linenoiseAddCompletion(lc,"hello");
        linenoiseAddCompletion(lc,"hello there");
    }
}

Basically in your completion callback, you inspect the input, and return a list of items that are good completions by using linenoiseAddCompletion.

If you want to test the completion feature, compile the example program with make, run it, type h and press <TAB>.

Hints

Linenoise has a feature called hints which is very useful when you use Linenoise in order to implement a REPL (Read Eval Print Loop) for a program that accepts commands and arguments, but may also be useful in other conditions.

The feature shows, on the right of the cursor, as the user types, hints that may be useful. The hints can be displayed using a different color compared to the color the user is typing, and can also be bold.

For example as the user starts to type "git remote add", with hints it's possible to show on the right of the prompt a string <name> <url>.

The feature works similarly to the history feature, using a callback. To register the callback we use:

linenoiseSetHintsCallback(hints);

The callback itself is implemented like this:

char *hints(const char *buf, int *color, int *bold) {
    if (!strcasecmp(buf,"git remote add")) {
        *color = 35;
        *bold = 0;
        return " <name> <url>";
    }
    return NULL;
}

The callback function returns the string that should be displayed or NULL if no hint is available for the text the user currently typed. The returned string will be trimmed as needed depending on the number of columns available on the screen.

It is possible to return a string allocated in dynamic way, by also registering a function to deallocate the hint string once used:

void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);

The free hint callback will just receive the pointer and free the string as needed (depending on how the hits callback allocated it).

As you can see in the example above, a color (in xterm color terminal codes) can be provided together with a bold attribute. If no color is set, the current terminal foreground color is used. If no bold attribute is set, non-bold text is printed.

Color codes are:

red = 31
green = 32
yellow = 33
blue = 34
magenta = 35
cyan = 36
white = 37;

Screen handling

Sometimes you may want to clear the screen as a result of something the user typed. You can do this by calling the following function:

void linenoiseClearScreen(void);

Asyncrhronous API

Sometimes you want to read from the keyboard but also from sockets or other external events, and at the same time there could be input to display to the user while the user is typing something. Let's call this the "IRC problem", since if you want to write an IRC client with linenoise, without using some fully featured libcurses approach, you will surely end having such an issue.

Fortunately now a multiplexing friendly API exists, and it is just what the blocking calls internally use. To start, we need to initialize a linenoise context like this:

struct linenoiseState ls;
char buf[1024];
linenoiseEditStart(&ls,-1,-1,buf,sizeof(buf),"some prompt> ");

The two -1 and -1 arguments are the stdin/out descriptors. If they are set to -1, linenoise will just use the default stdin/out file descriptors. Now as soon as we have data from stdin (and we know it via select(2) or some other way), we can ask linenoise to read the next character with:

linenoiseEditFeed(&ls);

The function returns a char pointer: if the user didn't yet press enter to provide a line to the program, it will return linenoiseEditMore, that means we need to call linenoiseEditFeed() again when more data is available. If the function returns non NULL, then this is a heap allocated data (to be freed with linenoiseFree()) representing the user input. When the function returns NULL, than the user pressed CTRL-C or CTRL-D with an empty line, to quit the program, or there was some I/O error.

After each line is received (or if you want to quit the program, and exit raw mode), the following function needs to be called:

linenoiseEditStop(&ls);

To start reading the next line, a new linenoiseEditStart() must be called, in order to reset the state, and so forth, so a typical event handler called when the standard input is readable, will work similarly to the example below:

void stdinHasSomeData(void) {
    char *line = linenoiseEditFeed(&LineNoiseState);
    if (line == linenoiseEditMore) return;
    linenoiseEditStop(&LineNoiseState);
    if (line == NULL) exit(0);

    printf("line: %s\n", line);
    linenoiseFree(line);
    linenoiseEditStart(&LineNoiseState,-1,-1,LineNoiseBuffer,sizeof(LineNoiseBuffer),"serial> ");
}

Now that we have a way to avoid blocking in the user input, we can use two calls to hide/show the edited line, so that it is possible to also show some input that we received (from socekts, bluetooth, whatever) on screen:

linenoiseHide(&ls);
printf("some data...\n");
linenoiseShow(&ls);

To the API calls, the linenoise example C file implements a multiplexing example using select(2) and the asynchronous API:

    struct linenoiseState ls;
    char buf[1024];
    linenoiseEditStart(&ls,-1,-1,buf,sizeof(buf),"hello> ");

    while(1) {
        // Select(2) setup code removed...
        retval = select(ls.ifd+1, &readfds, NULL, NULL, &tv);
        if (retval == -1) {
            perror("select()");
            exit(1);
        } else if (retval) {
            line = linenoiseEditFeed(&ls);
            /* A NULL return means: line editing is continuing.
             * Otherwise the user hit enter or stopped editing
             * (CTRL+C/D). */
            if (line != linenoiseEditMore) break;
        } else {
            // Timeout occurred
            static int counter = 0;
            linenoiseHide(&ls);
            printf("Async output %d.\n", counter++);
            linenoiseShow(&ls);
        }
    }
    linenoiseEditStop(&ls);
    if (line == NULL) exit(0); /* Ctrl+D/C. */

You can test the example by running the example program with the --async option.

Related projects

  • Linenoise NG is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language.
  • Linenoise-swift is a reimplementation of Linenoise written in Swift.

linenoise's People

Contributors

9ajiang avatar antirez avatar fbrusch avatar fperrad avatar gainspeed avatar gsserge avatar hoelzro avatar lifubang avatar martinnowak avatar pietern avatar polch avatar shengwen-tw avatar shvechikov avatar spullara avatar yossigo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

linenoise's Issues

Improve compatibility with VT100 and windows ANSI.SYS terminals

I notice that using a windows terminal program (CRT) in its default VT100 or any of its ANSI modes has display issues. I also notice issues when running telnet from a WIN7 command prompt.

Looking at the Wikipedia page for ANSI Escape Codes, I note that the CHA (ESC [ n G) sequence is not part of ANSI.SYS and I believe was not part of the VT100 command set (I think it was added in the VT220).

Looking at the code, it appears that cu

rrently it is used only to set the cursor to the first column. I replaced all of thos occurances with an CUB sequence to send the cursor back 999 coulums. This seems to work fine with all of the terminals that I tried. It may also be possible to replace the CHA 0 with a \r instead. Here is a patch:

diff --git a/linenoise.c b/linenoise.c
index aef5cdd..b5404b2 100644
--- a/linenoise.c
+++ b/linenoise.c
@@ -56,10 +56,6 @@
  * flickering effect with some slow terminal, but the lesser sequences
  * the more compatible.
  *
- * CHA (Cursor Horizontal Absolute)
- *    Sequence: ESC [ n G
- *    Effect: moves cursor to column n
- *
  * EL (Erase Line)
  *    Sequence: ESC [ n K
  *    Effect: if n is 0 or missing, clear from cursor to end of line
@@ -68,7 +64,19 @@
  *
  * CUF (CUrsor Forward)
  *    Sequence: ESC [ n C
- *    Effect: moves cursor forward of n chars
+ *    Effect: moves cursor forward n chars
+ *
+ * CUB (CUrsor Backward)
+ *    Sequence: ESC [ n D
+ *    Effect: moves cursor backward n chars
+ * 
+ * The following is used to get the terminal width if getting
+ * the width with the TIOCGWINSZ ioctl fails
+ *
+ * DSR (Device Status Report)
+ *    Sequence: ESC [ 6 n
+ *    Effect: reports the current cusor position as ESC [ n ; m R
+ *            where n is the row and m is the column
  *
  * When multi line mode is enabled, we also use an additional escape
  * sequence. However multi line editing is disabled by default.
@@ -471,7 +479,7 @@ static void refreshSingleLine(struct linenoiseState *l) {

     abInit(&ab);
     /* Cursor to left edge */
-    snprintf(seq,64,"\x1b[0G");
+    snprintf(seq,64,"\x1b[999D");
     abAppend(&ab,seq,strlen(seq));
     /* Write the prompt and the current buffer content */
     abAppend(&ab,l->prompt,strlen(l->prompt));
@@ -480,7 +488,7 @@ static void refreshSingleLine(struct linenoiseState *l) {
     snprintf(seq,64,"\x1b[0K");
     abAppend(&ab,seq,strlen(seq));
     /* Move cursor to original position. */
-    snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen));
+    snprintf(seq,64,"\x1b[999D\x1b[%dC", (int)(pos+plen));
     abAppend(&ab,seq,strlen(seq));
     if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
     abFree(&ab);
@@ -515,13 +523,13 @@ static void refreshMultiLine(struct linenoiseState *l) {
     /* Now for every row clear it, go up. */
     for (j = 0; j < old_rows-1; j++) {
         lndebug("clear+up");
-        snprintf(seq,64,"\x1b[0G\x1b[0K\x1b[1A");
+        snprintf(seq,64,"\x1b[999D\x1b[0K\x1b[1A");
         abAppend(&ab,seq,strlen(seq));
     }

     /* Clean the top line. */
     lndebug("clear");
-    snprintf(seq,64,"\x1b[0G\x1b[0K");
+    snprintf(seq,64,"\x1b[999D\x1b[0K");
     abAppend(&ab,seq,strlen(seq));

     /* Write the prompt and the current buffer content */
@@ -536,7 +544,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
     {
         lndebug("<newline>");
         abAppend(&ab,"\n",1);
-        snprintf(seq,64,"\x1b[0G");
+        snprintf(seq,64,"\x1b[999D");
         abAppend(&ab,seq,strlen(seq));
         rows++;
         if (rows > (int)l->maxrows) l->maxrows = rows;
@@ -555,7 +563,7 @@ static void refreshMultiLine(struct linenoiseState *l) {

     /* Set column. */
     lndebug("set col %d", 1+((plen+(int)l->pos) % (int)l->cols));
-    snprintf(seq,64,"\x1b[%dG", 1+((plen+(int)l->pos) % (int)l->cols));
+    snprintf(seq,64,"\x1b[999D\x1b[%dC", 1+((plen+(int)l->pos) % (int)l->cols));
     abAppend(&ab,seq,strlen(seq));

     lndebug("\n");
@@ -904,7 +912,7 @@ void linenoisePrintKeyCodes(void) {

         printf("'%c' %02x (%d) (type quit to exit)\n",
             isprint(c) ? c : '?', (int)c, (int)c);
-        printf("\x1b[0G"); /* Go left edge manually, we are in raw mode. */
+        printf("\x1b[999D"); /* Go left edge manually, we are in raw mode. */
         fflush(stdout);
     }
     disableRawMode(STDIN_FILENO);

readline(3) API ?

Hello, thanks for writing this small and efficient lib. It may be against its spirit but since it is BSD licensed, could you add a readline(3) compatible interface to it?

Delete Key

Hi,

The below patch adds support for delete, tested on linux vt + konsole(xterm). I'm unable to test on mac, sorry.

diff --git a/linenoise.c b/linenoise.c
index b7c6b73..8328efd 100644
--- a/linenoise.c
+++ b/linenoise.c
@@ -213,7 +213,7 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
while(1) {
char c;
int nread;

  •    char seq[2];
    
  •    char seq[2], seq2[2];
    
     nread = read(fd,&c,1);
     if (nread <= 0) return len;
    

    @@ -295,6 +295,19 @@ up_down_arrow:
    refreshLine(fd,prompt,buf,len,pos,cols);
    }
    }

  •        else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
    
  •            /\* extended escape */
    
  •            if (read(fd,seq2,2) == -1) break;
    
  •            if (seq[1] == 51 && seq2[0] == 126) {
    
  •                /\* delete */
    
  •                if (len > 0 && pos < len) {
    
  •                    memmove(buf+pos,buf+pos+1,len-pos-1);
    
  •                    len--;
    
  •                    buf[len] = '\0';
    
  •                    refreshLine(fd,prompt,buf,len,pos,cols);
    
  •                }
    
  •            }
    
  •        }
         break;
     default:
         if (len < buflen) {
    

True embedded support

I am using linenoise over serial on a naked (no OS) ARM Cortex-M4 embedded system. "Ported" the library in 20 seconds by just commenting out the termios, ioctl and file-system dependent stuff. It works perfectly. Does it make sense to apply some ifndef-love to save people like me the trouble of maintaining a separate fork? I can prepare a pull-request if the interest is there.

[PATCH] C++ compatibility and constant string arguments

  1. Wrapped the header file definitions in extern "C" { ... } when including the header file in C++ code.
  2. Changed function calls that does not modify string arguments to take constants. This solves two problems:
    1. Some compilers, like clang and gcc, will generate a warning when compiling with
      -Wdeprecated-writable-strings (e.g. -Wall or equiv).
      The warning looks something like this: warning: conversion from string literal
      to 'char *' is deprecated [-Wdeprecated-writable-strings]
    2. When using linenoise in C++ code, calls like this will simply not resolve to any
      implementations: linenoiseHistorySave("foo") This error happens since "foo" is
      of type const char* but the only implementation available for linenoiseHistorySave
      is (char*), thus epic fail.

https://gist.github.com/e8aa7f8e16578695471f

$ curl -o 0001-cxx-compatibility-and-constant-string-arguments.patch https://gist.github.com/e8aa7f8e16578695471f
$ git apply 0001-cxx-compatibility-and-constant-string-arguments.patch

linenoise eats a serial port

Using redis on an ARM embedded-system; however, when opening redis-cli on the serial console, I cannot see any characters, and also input doesn't seem to work, as typing commands blind doesn't do anything either.
Also, all control signals are consumed as well so I have to kill off the process to get control of the serial port back.

Using redis-cli over SSH to the same device works fine. After logging in I see there is a .rediscli-history file that has the commands that I was typing over SSH, but nothing for the serial port.

Add multibyte support

Current code doesn't have support for multibyte strings, e.g. strings having unicode characters beyond ASCII range. The column shifts for refreshLine are calculated using strlen() which returns 2 instead of 1 for a 2-byte character like 'ลž' in Turkish.

The library should use mbstowcs() or other functions to get the number of characters instead of number of bytes for column processing (up, down arrows, erasing a character, etc.).

And also as those functions are LC_CTYPE dependent, either you or the applications using linenoise should call setlocale(LC_ALL, "") to set the application's locale to the system locale.

Thanks.

incremental search?

Hi!

This library is awesome.

Do you plan to implement anything like incremental search of history (Ctrl-R)?

history search

provide a way to find last command containing substring like Ctrl-R in bash

Initial text support

Exclusively for the interactive input, it would be convenient to have a possibility not only to input text, but also to edit text supplied by the user. Like for example read -e -i <initial-text> allows to do in the bash.

I have made a simple patch - linenoiseInitial.diff.txt, it adds 15-20 lines. Not sure that's the right way to do it (changes how the linenoiseEdit() treats the buf) but it seems to be working.

Clearing of characters broken on Windows? (backspace / delete keys)

From arangodb/arangodb#1187:

  1. If you hit backspace with no character entered, arangosh [...]> disappears, and re-appears if you type something in
  2. If you delete characters and the code wraps to a 2nd line past the length of the string
    arangosh [...]>, the last character won't be cleared and will thus appear as many times as you hit Backspace / Del. The superfluous chars are removed as soon as the line length drops low enough (around character 80 of the entered code).

http://youtu.be/LxQ0zg-fOoY

Is this a known problem? Was it fixed meanwhile? If no, could you take a look why this happens?

Windows support?

Linenoise claims it supports ANSI.SYS terminals, which suggests Windows, however it appears to be impossible to compile it on Windows... or at least MSVC. It uses POSIX headers like termios.h, unistd.h, etc. which don't exist in that environment.

How feasible would it be to add MSVC support?

Problems in mintty/cygwin

Hi, I found my way here after diagnosing a problem in redis-cli.exe, built with gcc/x86_64-pc-cygwin/4.8.2. I'm running this in the mintty terminal (cygwin's xterm emulator), inside of gnu screen (but same issue outside of screen with TERM=xterm, etc).

I tried out the example.c client and see the same issues which are:

Issue A) linenoisePrompt writes the prompt at the previous line's last column.

Here is me pressing enter several times:

%>echo $TERM
screen-256color
%>./example.exe
hello>
       hello>
              hello>
                     hello>
                            hello>

While this problem goes away if I set TERM=dumb, so do most of the nice features.
I've tried using various TERM settings, and I've done a little debugging....

When I hit enter, it looks like printing "\n" is only causing a NL without a CR... if I step inside of linenoiseRaw, before the prompt is written, I can clearly see my cursor has moved down a line, but has not jumped back to column 0, and this is the place where the next line's prompt starts.

I don't know the termios apis that well, but I did a little poking around. If it helps, here is are my termios structs:

orig_termios = {
  c_iflag = 0,
  c_oflag = 0,
  c_cflag = 6306,
  c_lflag = 0,
  c_line = 0 '\000',
  c_cc = "\000\017\000\000\004\177\003\025\026\001\034\022\021\023\032\032\000\027",
  c_ispeed = 15,
  c_ospeed = 15
}
raw = {
  c_iflag = 0,
  c_oflag = 0,
  c_cflag = 6322, // The only actual change...
  c_lflag = 0,
  c_line = 0 '\000',
  c_cc = "\000\017\000\000\004\177\003\025\026\001\034\022\021\023\032\032\000\027",
  c_ispeed = 15,
  c_ospeed = 15
}

The only change here is the application of CS8, which has no effect if I don't change it. I also tried out some different combos of iflags and oflags, and can fix the problem with ECHO enabled, but that pretty much cripples every other feature.

I think the best fix is to explicitly move the cursor to column 0 before writing the prompt. Since you already use the ANSI CHA (Cursor Horizontal Absolute) escape sequence elsewhere, it seems like a sane thing to use here.

In linenoisePrompt, I changed:
    if (write(fd,prompt,plen) == -1) return -1;
to
    if (write(fd,"\x1b[0G",4) == -1) return -1;
    if (write(fd,prompt,plen) == -1) return -1;
or alternatively you could just call refreshLine with the prompt and empty buffer.

...and this fixes this issue for me. Perhaps there's a termios solution, but this seems like a simple enough catchall, assuming that the ANSI CHA sequence is working in general.

Issue B) arrows do not work.

When I press my up/down/left/right arrow, I just see an A/B/D/C character written to the terminal.

In the debugger, for up arrow I see the following sequence being sent:
(unsigned char) 27, 91, 153, 65
This is rather strange, because everywhere else I test it, mintty sends the usual 7 bit escape sequences for arrow keys:
(unsigned char) 27, 91, 65

It's only in this particular raw mode, it seems, where I get this 153 unsigned char in the mix. So, what is that 8 bit (153 / \231 / 0x99) character doing in there? Again, I tried not setting CS8 in raw.c_cflag, but that causes no change in what I'm seeing.

I'm really baffled by this one. If I run this example.exe build in a windows cmd.exe console, arrow keys work fine. And again, everywhere else I try mintty like at a bash prompt Ctrl-V,arrow_key, cat | xxd,arrow_key, with the exception being in vim where application key codes are sent like 27, 79, 65, which is normal.

I'd suggest someone else testing this in a cygwin/mintty setup to rule out that it's just something odd about my host machine / env. Thanks.

Multi-word completion

Is it possible to implement word by word completion with linenoise?
Lets assume we have commands:

command opt1 opt2
command opt3 opt4 opt5.
command opt7 opt8
command opt7 opt9

How can i organize completions to suggest for eaxample
"command" on typing any left substring of it then cycling through
next available options?

Newline (\n) not doing \r on Linux ($TERM = xterm)

I feel like I must be doing something wrong as I can't really imagine being the first to notice this behavior. So I apologize in advance if I'm just being silly here.

I'm using linenoise in a multi-threaded application that accepts user-input on terminal but can also output text on its own (for example, in case of errors it prints an error message).
We used to use libreadline, but it's no longer possible because of its license.

The error messages are just printed as:

std::cout << "Line one\n";
std::cout << "Line two\n";

With linenoise, the output is as follows:

Line one
        Line two

So 'Line two' is on a new line, but it starts right after 'Line one', rather than at the beginning of the line. It is as if the implicit \r is missing.
It's solved by using "\r\n" instead of \n as newline but it's kind of awkward to change this all over the place.

Note: clearly, whenever something is printed as a result of user-input, everything is fine. e.g.

> help
No problem
with newlines
here

Is this a known issue? Can I work around it without having to sed all of my \n into \r\n?

Thank you for your time.

linenoiseHistorySetMaxLen in loses data (if history_len < history_max_len!!)

Hi,

It seems to me that the copy of the history done in linenoiseHistorySetMaxLen loses data and actually corrupts the history (because the new copy is not zero-initialized). Consider this line:

memcpy(new,history+(history_max_len-tocopy), sizeof(char_)_tocopy);

with tocopy being set before:

int tocopy = history_len;

If history_len < history_max_len, copying starts somewhere at the end of the history array.

I think the correct code would be

int tocopy = history_max_len;

in line 577.

Best,
Michael

Consider adding extern "C" {...} to linenoise.h

Hi,

I hit this bug while trying to link linenoise (c code) from C++ code.

By adding this magical "extern "C"" the linker issue was fixed.

I asked on gyp mailing list (thinking the problem was with my gyp files), but it turns out, it wasn't gyp:
https://groups.google.com/d/topic/gyp-developer/mq-NR67ME9U/discussion

See the fix I just pushed at:
https://github.com/tfarina/cmdline_history/commit/323960257137349cfa142beb750c86f87bfbc64e

So you may consider adding it to your linenoise.h file.

Best regards,

Provide testing steps

Testing linenoise in an automatic way is very hard in a general way, assuming any kind of terminal. However as a start we could at least provide a list of editing steps the user should perform to check if it works more or less correctly.

The second step could be to provide a semi-automated test simulating the keystroke input in software, and asking the user at different steps to verify that the output on the screen is the expected one.

Use \e for editing the line with your $EDITOR

Especially with Redis scripting this is a really needed feature.
Worth adding it directly in Linenoise instead of implementing it in redis-cli so that it can be used by all the projects using linenoise.

Multi-threading problem

Hello, this library is awesome and I use it in my tools. But I have some problem.

In my tool, I use libuv to do the network jobs and use linenoise to interactive with user. They are run on different thread. But when running linenoise and blocked on one thread and writing some message with '\n' on another thread, the column position of the next line is padding to the position of the preview's.
For example, if I write abc\ndef to stdout, it will be:

~>    
# terminal show this and then I call printf("abc\ndef") on another thread.
# then it will be like this
~>abc
     def

It's hard for humen to read especially the line is long and there is many line.Such as I print the DebugString of protobuf to stdout.

Then I fork this repo and comment linenoise.c:233

    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* output modes - disable post processing */
    // raw.c_oflag &= ~(OPOST);  // HERE MODIFIED
    /* control modes - set 8 bit chars */
    raw.c_cflag |= (CS8);

And then this problem is gone.
But I wonder if is there any problem in other cases?
Hope for your help.

Thanks

write to STDOUT_FILENO

Received via email from a kind user:


I have a question about the internal implementation: why are you writing in STDIN_FILENO? Linenoise is not working as-is in my system because stdin is not the same thing that stdout (two unidirectionals pipes that link on an UART driver).

I replace every 'write(fd' by 'write(STDOUT_FILENO' and it is working like a charm (hence the above question).

BTW, is it possible to add a way of managing the differents end-of-line? (\r\n, \r, \n, \r\0)

ctrl-b weirdness when there is leading tabs

Simple test case:

  • run make
  • run libnoise_example
  • press tab twice
  • type foo
  • press space one or more times
  • type bar
  • press ctrl-b

You'll see that rather than moving the cursor back over the r in bar, it moves it to the space before the f in foo. On top of that, if you start pressing ctrl-f, you will eventually be unable to move past the f. This is because the cursor seems to be at the end of line already. Any thing typed after hitting that invisible wall, will be echoed out as occuring after the bar when you press enter.

This seems to be a regular thing when lines have tabs in them during a ctrl-b/left arrow event.

Signal handling

It seems since linenoise is using raw mode, the appropriate signals aren't being raised when ctrl+c, ctrl+z, etc. are used.

Provide a make target for libraries

I am compelled to make this suggestion by the homebrew team as part of my attempt to add a linenoise formula there. To make linenoise install as a library on OS X, the following patch is required to the Makefile:

+--- a/Makefile
++++ b/Makefile
+@@ -1,7 +1,15 @@
++all: liblinenoise.dylib linenoise_example
++
++liblinenoise.dylib: linenoise.o
++  $(CC) -dynamiclib -o liblinenoise.dylib linenoise.o
++
++%.o: %.c
++  $(CC) $(CFLAGS) -o $@ -c $<
++
+ linenoise_example: linenoise.h linenoise.c
+
+ linenoise_example: linenoise.c example.c
+   $(CC) -Wall -W -Os -g -o linenoise_example linenoise.c example.c
+
+ clean:
+-  rm -f linenoise_example
++  rm -f linenoise_example liblinenoise.dylib

Something like this would be handy for packaging systems wishing to install linenoise as a dynamic library and is further handy to bindings in languages like Perl 6 which work best with dynamic libraries. Obviously, this patch is not useful in a general way as it only works on OS X.

Cancel linenoise input on signal, I/O error, or close (read 0).

If the user, when entering a line, presses C-c, the operation is canceled, and, effectivelly, linenoise() returns NULL. This is the correct behavior IMHO.

On the other hand, if, when entering a line, a signal is received or an input error is encountered, or if the channel is closed (read(2) returns 0), then linenoise() returns the partial line without any indication that something went wrong.

I think the following patch (or something along these lines) should be applied to correct this:

npat-efault/picocom@352ed00bc0

Multi-line paste fails

I have a terminal program that I've added linenoise to, and in doing so, I've lost the ability to paste multiple lines of input into the program at once (in some terminals). It looks like https://github.com/antirez/linenoise/blob/master/linenoise.c#L200 is largely responsible for this, as the input buffer is flushed at the first \n, resulting in the extra paste information being lost. Any chance this extra data could be buffered and pushed into subsequent invocations of linenoisePrompt?

Support long lines

As linenoise is used by redis, it is not possible to set long key values that exceed 4K.

When linenoise reads in a line and does not find the \n, it should continue to read in more blocks of input and concatenate them until the \n is found.

GTK compatible?

Hi,
Is it possible to use linenoise in a graphical GTK application ?
If yes, how to deal with blocking linenoise() calls in the loop, and the gtk_main() function?

Single ESC or ESC Sequence?

Use the example program.

Get yourself into completeLine().
Press escape - original buffer is restored. Good.

Get yourself into completeLine().
Press left arrow - original buffer is restored. Not good.
The user would expect a left cursor movement.

If you do press escape to restore the line, the next 2 key entries are absorbed into the escape sequence processing of linenoiseEdit(). That's not good.

If you press escape during normal line editing the next 2 key entries are absorbed into the escape sequence processing of linenoiseEdit(). That's not good.

Problem: The code (completeLine/linenoiseEdit) can't distinguish between a lone escape and an escape sequence.

Idea: fix it by checking the readability of the fd after having seen the escape.

  • If it's not readable with N ms then its a single escape.
  • If it is readable within N ms then it's (probably) an escape sequence.

Pasting multiple lines doesn't work

When trying to paste multiple lines into an application using linenoise, only the first line is taken. As an example, compiling the example applicacation and trying to paste

test1
test2
test3
test4
test5

into the example application gives:

lash@bill17laptop:~/linenoisepaste/linenoise$ ./linenoise_example
hello> test1
echo: 'test1'
hello>

This seems to be because of the use of TCAFLUSH in enableRawMode and disableRawMode. By changing thos to TCADRAIN, I see the expected behavior:

lash@bill17laptop:~/linenoisepaste/linenoise$ ./linenoise_example
hello> test1
echo: 'test1'
hello> test2
echo: 'test2'
hello> test3
echo: 'test3'
hello> test4
echo: 'test4'
hello> test5
echo: 'test5'
hello>

Here is the patch that I used:

lash@bill17laptop:~/linenoisepaste/linenoise$ git diff
diff --git a/linenoise.c b/linenoise.c
index 36c0c5f..572c34a 100644
--- a/linenoise.c
+++ b/linenoise.c
@@ -238,7 +238,7 @@ static int enableRawMode(int fd) {
     raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */

     /* put terminal in raw mode after flushing */
-    if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
+    if (tcsetattr(fd,TCSADRAIN,&raw) < 0) goto fatal;
     rawmode = 1;
     return 0;

@@ -249,7 +249,7 @@ fatal:

 static void disableRawMode(int fd) {
     /* Don't even check the return value as it's too late. */
-    if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
+    if (rawmode && tcsetattr(fd,TCSADRAIN,&orig_termios) != -1)
         rawmode = 0;
 }

"Call to 'realloc' has an allocation size of 0 bytes" - static analyzer warning.

Feel free to close this.. But my static analyzer popped up with this "observation". Not quite sure if it is erroneous, or not.. so thought I'd just put it up.

linenoise.c: 447:17: Call to 'realloc' has an allocation size of 0 bytes
linenoise.c: 751:11: Entering loop body
linenoise.c: 757:13: Assuming 'nread' is > 0
linenoise.c: 762:13: Assuming 'c' is not equal to 9
linenoise.c: 751:5: Looping back to the head of the loop
linenoise.c: 751:11: Entering loop body
linenoise.c: 757:13: Assuming 'nread' is > 0
linenoise.c: 762:13: Assuming 'c' is not equal to 9
linenoise.c: 751:5: Looping back to the head of the loop
linenoise.c: 751:11: Entering loop body
linenoise.c: 757:13: Assuming 'nread' is > 0
linenoise.c: 762:13: Assuming 'c' is not equal to 9
linenoise.c: 751:5: Looping back to the head of the loop
linenoise.c: 751:11: Entering loop body
linenoise.c: 757:13: Assuming 'nread' is > 0
linenoise.c: 762:13: Assuming 'c' is not equal to 9
linenoise.c: 890:13: Calling 'refreshLine'
linenoise.c: 584:1: Entered call from '> linenoiseEdit'
linenoise.c: 585:9: Assuming 'mlmode' is not equal to 0
linenoise.c: 586:9: Calling 'refreshMultiLine'
linenoise.c: 502:1: Entered call from 'refreshLine'
linenoise.c: 526:17: Loop body executed 0 times
linenoise.c: 539:5: Calling 'abAppend'
linenoise.c: 446:1: Entered call from 'refreshMultiLine'
linenoise.c: 447:17: Call to 'realloc' has an allocation size of 0 bytes

2015-04-14_05-19-54

GCC warnings

I think will be good idea to fix next GCC warnings:

linenoiseHistoryAdd() is defined in linenoise.h.

If functions without prototypes in linenoise.h are not intended to be used outside linenoise.c they should be made static.

Compiling C file linenoise.c (debug + pic)
linenoise.c:173:5: warning: redundant redeclaration of 'linenoiseHistoryAdd' [-Wredundant-decls]
int linenoiseHistoryAdd(const char *line);
^
In file included from linenoise.c:117:0:
linenoise.h:54:5: note: previous declaration of 'linenoiseHistoryAdd' was here
int linenoiseHistoryAdd(const char *line);
^
linenoise.c:594:5: warning: no previous prototype for 'linenoiseEditInsert' [-Wmissing-prototypes]
int linenoiseEditInsert(struct linenoiseState *l, char c) {
^
linenoise.c:621:6: warning: no previous prototype for 'linenoiseEditMoveLeft' [-Wmissing-prototypes]
void linenoiseEditMoveLeft(struct linenoiseState *l) {
^
linenoise.c:629:6: warning: no previous prototype for 'linenoiseEditMoveRight' [-Wmissing-prototypes]
void linenoiseEditMoveRight(struct linenoiseState *l) {
^
linenoise.c:637:6: warning: no previous prototype for 'linenoiseEditMoveHome' [-Wmissing-prototypes]
void linenoiseEditMoveHome(struct linenoiseState *l) {
^
linenoise.c:645:6: warning: no previous prototype for 'linenoiseEditMoveEnd' [-Wmissing-prototypes]
void linenoiseEditMoveEnd(struct linenoiseState *l) {
^
linenoise.c:656:6: warning: no previous prototype for 'linenoiseEditHistoryNext' [-Wmissing-prototypes]
void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
^
linenoise.c:680:6: warning: no previous prototype for 'linenoiseEditDelete' [-Wmissing-prototypes]
void linenoiseEditDelete(struct linenoiseState *l) {
^
linenoise.c:690:6: warning: no previous prototype for 'linenoiseEditBackspace' [-Wmissing-prototypes]
void linenoiseEditBackspace(struct linenoiseState *l) {
^
linenoise.c:702:6: warning: no previous prototype for 'linenoiseEditDeletePrevWord' [-Wmissing-prototypes]
void linenoiseEditDeletePrevWord(struct linenoiseState *l) {

linenoiseHistoryAdd args

Hi,
thanks for your efforts, looks like a nice piece of code!
I would change
int linenoiseHistoryAdd(char * line);
to
int linenoiseHistoryAdd(const char * line);
You are using strdup inside it so its more clear and consistent to declare its argument const.
My 2 cent

Claudio

Add "ansi" to `unsupported_term`

In my admittedly idiosyncratic setup (I cargo-culted it from somewhere so there's at least one other person doing this, but probably not many), I use emacs shells with TERM=ansi, because my emacs configuration supports basic ANSI escapes (color etc) but not fancier things like cursor positioning. In linenoise this should be treated as a dumb/unsupported terminal.

Carriage returns not working in background

I have tail -f running in the background while launching a linenoise shell. Newlines seem to be passing through fine, but the carriage return isn't being handled. I'm not sure whether linenoise is to blame, but when I run a bash shell with the same tail process backgrounded, I don't see this behavior.

I/O garbled in Xcode's "pseudo" PTY

you DID say to test it everywhere.... and although the Xcode console is far from compliant with ANYthing.. it would be nice if there was a sane way to interact with it.. which is what led me to your (neat) project.

everything is fine and dandy in iTerm...

screen shot 2015-02-22 at 8 29 46 am

but here's what happens in good'ole Xcode (6.1.1)...

linenoise

arguably, this IS better (though far from ideal)... than what you get with my normal IO routines in the same console environment...

stdio

The Xcode console is very weird.. It has no exposed "size" (COLUMNS, and the such), and also, mine MAY be off kilter a bit.. as I DO have the XCODE_COLORS extension installed (amongst many others). Maybe someone with a stock rig can try it, as well?

Let me know how I can help fix this... if you (and God, ๐Ÿ™) are wiling...

Too much code!

The README claims under 1000 lines, but linenoise.c is already 1091 lines in c86280e.

๐Ÿ˜ƒ

Improper use of snprintf return value (buffer overflow)

At line [email protected], snprintf is returning the number of characters that should have been written to the buffer, not the actual written characters.
The returned value needs to be checked against the buflen limit.

This should be enough for a fast fix:
if(nwritten >= ls->buflen)
nwritten = ls->buflen - 1;

create release tag

Hi,

It would be very helpful for packagers if you could create a release tag. v0.1 or whatever.

Thanks!

vi mode?

This would seriously up the line count, but I'm also an addict that can't get enough of vi/vim.

Some knowledge of modes, with support for Esc, i, h, l, j & k are the minimal requirements.

linenoise + ncurses (or other libs)

I'm wondering if is it possible to use linenoise under ncurses. I saw that linenoise() function use printf(), but I need to use the ncurses (printw, wprintw, mvprintw, mvwprintw, ...) functions.
Can I make linenoise work without the use of this function linenoise()?
If no, even that people can simply change the linenoise() function, why not make linenoise transparent in the sense of only return the data (chars, strings, values) and let people print and handle it the way they want? This will make possible the use of linenoise in any type or software, and use linenoise anywhere; like graphical applications.

Thanks, (correct me if i'm wrong)
Henrique Lengler

Win32 port

Hi,

I needed a win32 port. The attached is not particularly clean, but it works fine for me. Compiled with VS 2005. I did it as a separate file since ifdef'ing the existing code got ugly, and it really needs to support raw console mode if its ever going to support emacs key bindings.

I may get time to improve this later in which case I'll update it here. Currently it supports basic line editing and home/end, but no ctrl+key shortcuts, i.e. enough to scratch my immediate itch. If you #define ALT_KEYS you can have ALT+key mappings, but after trying this for a while, my fingers couldn't really adjust to it.

Cheers,
Jon

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.