33 Commits

Author SHA1 Message Date
antirez
3476ccc9c7 Multiplexing: README updated. 2023-03-27 10:36:03 +02:00
antirez
8087db33d8 Multiline: just remember last num of rows, not max.
For some reason, the old code was always cleaning the maximum number of
rows used so far while editing in multi line mode. Actually we need to
clean just the number of rows used by the last line. The old behavior
created problems in multiplexing mode, where the line is refreshed at a
different row, if the user used linenoiseHide() / show() in order to
print something. With the new behavior, all looks fine, so far.
2023-03-27 10:07:47 +02:00
antirez
81f44df639 Multiplexing: fix line refresh in completion mode. 2023-03-27 09:36:31 +02:00
antirez
a1d8e181c2 Multiplexing: make completion non-blocking as well. 2023-03-27 09:21:31 +02:00
antirez
65db823f8b Some documentation and comments updates. 2023-03-26 23:27:33 +02:00
antirez
c9d36d0681 Multiplexing: API refactoring, no TTY support. 2023-03-26 22:22:40 +02:00
antirez
dbfe83bb67 Multiplexing: fix refreshMultiLine(). 2023-03-26 16:44:29 +02:00
antirez
622c777f41 Multiplexing: implement example using it. 2023-03-26 16:42:22 +02:00
antirez
c9123ec3c6 Multiplexing: hide/show current line. 2023-03-26 13:23:59 +02:00
antirez
0d66aaca11 Multiplexing: code refactored into calls for each step. 2023-03-26 11:04:28 +02:00
antirez
97d2850af1 Use unsigned int instead of uint like rest of code base. 2020-03-12 15:51:45 +01:00
Salvatore Sanfilippo
4ce393a66b Merge pull request #185 from yossigo/fix-c99-warning
Fix compilation in non C99/C11 mode.
2020-03-12 15:48:04 +01:00
Yossi Gottlieb
8c1c63c5fd Fix compilation in non C99/C11 mode. 2020-03-12 14:48:36 +02:00
Salvatore Sanfilippo
fc9667a81d Merge pull request #183 from lifubang/fixmasking
fix masking input when there is no hintsCallback
2020-03-05 10:44:35 +01:00
lifubang
ec5e4e8716 fix masking input when there is no hintsCallback
Signed-off-by: lifubang <lifubang@acmcoder.com>
2020-03-03 11:51:08 +08:00
antirez
4261898b11 A few improvements to mask mode. 2020-03-02 17:06:38 +01:00
Salvatore Sanfilippo
f31e883a08 Merge pull request #182 from lifubang/masking
add mask input mode
2020-03-02 16:56:27 +01:00
lifubang
514b09cd35 add mask input mode
Signed-off-by: lifubang <lifubang@acmcoder.com>
2020-03-01 18:32:11 +08:00
Salvatore Sanfilippo
4a961c0108 Merge pull request #151 from hoelzro/master
Set seq to empty string if color/bold not used
2018-07-18 18:29:05 +02:00
Salvatore Sanfilippo
cc2ea638ee Merge pull request #152 from fbrusch/master
Update README.markdown
2018-07-18 18:27:56 +02:00
fbrusch
cc53ed4bb0 Update README.markdown
Fix little typo
2018-04-22 12:43:43 +02:00
Rob Hoelz
b12f6ba9cb Set seq to empty string if color/bold not used
Otherwise the subsequent abAppend will concatenate whatever junk data
is in seq to ab
2018-02-15 22:38:41 -06:00
antirez
2105ce4458 README: add related projects section. 2017-06-20 11:30:07 +02:00
antirez
c894b9e59f Fix insecure history file creation.
See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=832460.

This patch was kindly contributed by Chris Lamb (@lamby).
2016-07-29 11:25:35 +02:00
antirez
2eb4956846 Clear hints after newline. 2016-04-13 12:19:04 +02:00
antirez
a64257a8d2 Hints: when only bold is set, use color 37 (white). 2016-04-12 18:44:46 +02:00
antirez
24c52baad6 Linenoise API documented. 2016-04-12 18:39:43 +02:00
antirez
24e401c202 Use sane defaults for hints color and bold. 2016-04-12 18:35:12 +02:00
antirez
ae5f793ec0 linenoiseFree() API introduced. 2016-04-12 18:08:33 +02:00
antirez
12a8680d8a Hints WIP 2016-04-12 17:59:41 +02:00
antirez
d97e7665a8 Copyright info updated. 2016-04-06 13:40:15 +02:00
antirez
94d9ddb256 4096 bytes line limit removed when STDIN is not a tty. 2016-04-06 13:39:09 +02:00
antirez
027dbcef5d Reported to work with Emacs comint mode. 2015-07-13 16:04:24 +02:00
4 changed files with 957 additions and 332 deletions

View File

@@ -1,12 +1,14 @@
# Linenoise
A minimal, zero-config, BSD licensed, readline replacement used in Redis,
MongoDB, and Android.
MongoDB, Android and many other projects.
* Single and multi line editing mode with the usual key bindings implemented.
* History handling.
* Completion.
* About 1,100 lines of BSD license source code.
* 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?
@@ -15,19 +17,21 @@ Line editing with some support for history is a really important feature for com
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).
* 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, supporing 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.
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 1100 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software.
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...
@@ -41,12 +45,287 @@ The library is currently about 1100 lines of code. In order to use it in your pr
* 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
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, and
we can read the next line again with `linenoiseEditFeed(&ls)` calls.
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.
Finally, before exiting the program, we need to exit raw mode and do other
clenaup. So we call:
linenoiseEditStop(&ls);
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 show all this, the linenoise example C file implements a multiplexing
example using select(2) and the asynchronous API:
```c
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](https://github.com/arangodb/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](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.

View File

@@ -1,9 +1,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include "linenoise.h"
void completion(const char *buf, linenoiseCompletions *lc) {
if (buf[0] == 'h') {
linenoiseAddCompletion(lc,"hello");
@@ -11,9 +11,19 @@ void completion(const char *buf, linenoiseCompletions *lc) {
}
}
char *hints(const char *buf, int *color, int *bold) {
if (!strcasecmp(buf,"hello")) {
*color = 35;
*bold = 0;
return " World";
}
return NULL;
}
int main(int argc, char **argv) {
char *line;
char *prgname = argv[0];
int async = 0;
/* Parse options, with --multiline we enable multi line editing. */
while(argc > 1) {
@@ -25,8 +35,10 @@ int main(int argc, char **argv) {
} else if (!strcmp(*argv,"--keycodes")) {
linenoisePrintKeyCodes();
exit(0);
} else if (!strcmp(*argv,"--async")) {
async = 1;
} else {
fprintf(stderr, "Usage: %s [--multiline] [--keycodes]\n", prgname);
fprintf(stderr, "Usage: %s [--multiline] [--keycodes] [--async]\n", prgname);
exit(1);
}
}
@@ -34,6 +46,7 @@ int main(int argc, char **argv) {
/* Set the completion callback. This will be called every time the
* user uses the <tab> key. */
linenoiseSetCompletionCallback(completion);
linenoiseSetHintsCallback(hints);
/* Load history from file. The history file is just a plain text file
* where entries are separated by newlines. */
@@ -45,7 +58,50 @@ int main(int argc, char **argv) {
*
* The typed string is returned as a malloc() allocated string by
* linenoise, so the user needs to free() it. */
while((line = linenoise("hello> ")) != NULL) {
while(1) {
if (!async) {
line = linenoise("hello> ");
if (line == NULL) break;
} else {
/* Asynchronous mode using the multiplexing API: wait for
* data on stdin, and simulate async data coming from some source
* using the select(2) timeout. */
struct linenoiseState ls;
char buf[1024];
linenoiseEditStart(&ls,-1,-1,buf,sizeof(buf),"hello> ");
while(1) {
fd_set readfds;
struct timeval tv;
int retval;
FD_ZERO(&readfds);
FD_SET(ls.ifd, &readfds);
tv.tv_sec = 1; // 1 sec timeout
tv.tv_usec = 0;
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. */
}
/* Do something with the string. */
if (line[0] != '\0' && line[0] != '/') {
printf("echo: '%s'\n", line);
@@ -55,6 +111,10 @@ int main(int argc, char **argv) {
/* The "/historylen" command will change the history len. */
int len = atoi(line+11);
linenoiseHistorySetMaxLen(len);
} else if (!strncmp(line, "/mask", 5)) {
linenoiseMaskModeEnable();
} else if (!strncmp(line, "/unmask", 7)) {
linenoiseMaskModeDisable();
} else if (line[0] == '/') {
printf("Unreconized command: %s\n", line);
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2023, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
@@ -43,23 +43,68 @@
extern "C" {
#endif
#include <stddef.h> /* For size_t. */
extern char *linenoiseEditMore;
/* The linenoiseState structure represents the state during line editing.
* We pass this state to functions implementing specific editing
* functionalities. */
struct linenoiseState {
int in_completion; /* The user pressed TAB and we are now in completion
* mode, so input is handled by completeLine(). */
size_t completion_idx; /* Index of next completion to propose. */
int ifd; /* Terminal stdin file descriptor. */
int ofd; /* Terminal stdout file descriptor. */
char *buf; /* Edited line buffer. */
size_t buflen; /* Edited line buffer size. */
const char *prompt; /* Prompt to display. */
size_t plen; /* Prompt length. */
size_t pos; /* Current cursor position. */
size_t oldpos; /* Previous refresh cursor position. */
size_t len; /* Current edited line length. */
size_t cols; /* Number of columns in terminal. */
size_t oldrows; /* Rows used by last refrehsed line (multiline mode) */
int history_index; /* The history index we are currently editing. */
};
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
} linenoiseCompletions;
/* Non blocking API. */
int linenoiseEditStart(struct linenoiseState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt);
char *linenoiseEditFeed(struct linenoiseState *l);
void linenoiseEditStop(struct linenoiseState *l);
void linenoiseHide(struct linenoiseState *l);
void linenoiseShow(struct linenoiseState *l);
/* Blocking API. */
char *linenoise(const char *prompt);
void linenoiseFree(void *ptr);
/* Completion API. */
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
typedef void(linenoiseFreeHintsCallback)(void *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
char *linenoise(const char *prompt);
/* History API. */
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
/* Other utilities. */
void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);
void linenoiseMaskModeEnable(void);
void linenoiseMaskModeDisable(void);
#ifdef __cplusplus
}