Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
redis
Commits
37031126
Commit
37031126
authored
Mar 13, 2014
by
antirez
Browse files
Linenoise updated, multiline mode enabled in redis-cli.
parent
ccd2c18c
Changes
3
Hide whitespace changes
Inline
Side-by-side
deps/linenoise/linenoise.c
View file @
37031126
...
@@ -10,8 +10,8 @@
...
@@ -10,8 +10,8 @@
*
*
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
*
*
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010
-2013
, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2010
-2013
, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
*
* All rights reserved.
* All rights reserved.
*
*
...
@@ -45,12 +45,10 @@
...
@@ -45,12 +45,10 @@
* - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
* - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
*
*
* Todo list:
* Todo list:
* - Switch to gets() if $TERM is something we can't support.
* - Filter bogus Ctrl+<char> combinations.
* - Filter bogus Ctrl+<char> combinations.
* - Win32 support
* - Win32 support
*
*
* Bloat:
* Bloat:
* - Completion?
* - History search like Ctrl+r in readline?
* - History search like Ctrl+r in readline?
*
*
* List of escape sequences used by this program, we do everything just
* List of escape sequences used by this program, we do everything just
...
@@ -72,6 +70,17 @@
...
@@ -72,6 +70,17 @@
* Sequence: ESC [ n C
* Sequence: ESC [ n C
* Effect: moves cursor forward of n chars
* Effect: moves cursor forward of n chars
*
*
* When multi line mode is enabled, we also use an additional escape
* sequence. However multi line editing is disabled by default.
*
* CUU (Cursor Up)
* Sequence: ESC [ n A
* Effect: moves cursor up of n chars.
*
* CUD (Cursor Down)
* Sequence: ESC [ n B
* Effect: moves cursor down of n chars.
*
* The following are used to clear the screen: ESC [ H ESC [ 2 J
* The following are used to clear the screen: ESC [ H ESC [ 2 J
* This is actually composed of two sequences:
* This is actually composed of two sequences:
*
*
...
@@ -92,6 +101,7 @@
...
@@ -92,6 +101,7 @@
#include <errno.h>
#include <errno.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <unistd.h>
...
@@ -99,19 +109,89 @@
...
@@ -99,19 +109,89 @@
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096
#define LINENOISE_MAX_LINE 4096
static
char
*
unsupported_term
[]
=
{
"dumb"
,
"cons25"
,
NULL
};
static
char
*
unsupported_term
[]
=
{
"dumb"
,
"cons25"
,
"emacs"
,
NULL
};
static
linenoiseCompletionCallback
*
completionCallback
=
NULL
;
static
linenoiseCompletionCallback
*
completionCallback
=
NULL
;
static
struct
termios
orig_termios
;
/* in order to restore at exit */
static
struct
termios
orig_termios
;
/* In order to restore at exit.*/
static
int
rawmode
=
0
;
/* for atexit() function to check if restore is needed*/
static
int
rawmode
=
0
;
/* For atexit() function to check if restore is needed*/
static
int
atexit_registered
=
0
;
/* register atexit just 1 time */
static
int
mlmode
=
0
;
/* Multi line mode. Default is single line. */
static
int
atexit_registered
=
0
;
/* Register atexit just 1 time. */
static
int
history_max_len
=
LINENOISE_DEFAULT_HISTORY_MAX_LEN
;
static
int
history_max_len
=
LINENOISE_DEFAULT_HISTORY_MAX_LEN
;
static
int
history_len
=
0
;
static
int
history_len
=
0
;
char
**
history
=
NULL
;
static
char
**
history
=
NULL
;
/* The linenoiseState structure represents the state during line editing.
* We pass this state to functions implementing specific editing
* functionalities. */
struct
linenoiseState
{
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
maxrows
;
/* Maximum num of rows used so far (multiline mode) */
int
history_index
;
/* The history index we are currently editing. */
};
enum
KEY_ACTION
{
KEY_NULL
=
0
,
/* NULL */
CTRL_A
=
1
,
/* Ctrl+a */
CTRL_B
=
2
,
/* Ctrl-b */
CTRL_C
=
3
,
/* Ctrl-c */
CTRL_D
=
4
,
/* Ctrl-d */
CTRL_E
=
5
,
/* Ctrl-e */
CTRL_F
=
6
,
/* Ctrl-f */
CTRL_H
=
8
,
/* Ctrl-h */
TAB
=
9
,
/* Tab */
CTRL_K
=
11
,
/* Ctrl+k */
CTRL_L
=
12
,
/* Ctrl+l */
ENTER
=
13
,
/* Enter */
CTRL_N
=
14
,
/* Ctrl-n */
CTRL_P
=
16
,
/* Ctrl-p */
CTRL_T
=
20
,
/* Ctrl-t */
CTRL_U
=
21
,
/* Ctrl+u */
CTRL_W
=
23
,
/* Ctrl+w */
ESC
=
27
,
/* Escape */
BACKSPACE
=
127
/* Backspace */
};
static
void
linenoiseAtExit
(
void
);
static
void
linenoiseAtExit
(
void
);
int
linenoiseHistoryAdd
(
const
char
*
line
);
int
linenoiseHistoryAdd
(
const
char
*
line
);
static
void
refreshLine
(
struct
linenoiseState
*
l
);
/* Debugging macro. */
#if 0
FILE *lndebug_fp = NULL;
#define lndebug(...) \
do { \
if (lndebug_fp == NULL) { \
lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
fprintf(lndebug_fp, \
"[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
(int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
(int)l->maxrows,old_rows); \
} \
fprintf(lndebug_fp, ", " __VA_ARGS__); \
fflush(lndebug_fp); \
} while (0)
#else
#define lndebug(fmt, ...)
#endif
/* ======================= Low level terminal handling ====================== */
/* Set if to use or not the multi line mode. */
void
linenoiseSetMultiLine
(
int
ml
)
{
mlmode
=
ml
;
}
/* Return true if the terminal name is in the list of terminals we know are
* not able to understand basic escape sequences. */
static
int
isUnsupportedTerm
(
void
)
{
static
int
isUnsupportedTerm
(
void
)
{
char
*
term
=
getenv
(
"TERM"
);
char
*
term
=
getenv
(
"TERM"
);
int
j
;
int
j
;
...
@@ -122,16 +202,7 @@ static int isUnsupportedTerm(void) {
...
@@ -122,16 +202,7 @@ static int isUnsupportedTerm(void) {
return
0
;
return
0
;
}
}
static
void
freeHistory
(
void
)
{
/* Raw mode: 1960 magic shit. */
if
(
history
)
{
int
j
;
for
(
j
=
0
;
j
<
history_len
;
j
++
)
free
(
history
[
j
]);
free
(
history
);
}
}
static
int
enableRawMode
(
int
fd
)
{
static
int
enableRawMode
(
int
fd
)
{
struct
termios
raw
;
struct
termios
raw
;
...
@@ -173,51 +244,83 @@ static void disableRawMode(int fd) {
...
@@ -173,51 +244,83 @@ static void disableRawMode(int fd) {
rawmode
=
0
;
rawmode
=
0
;
}
}
/* At exit we'll try to fix the terminal to the initial conditions. */
/* Use the ESC [6n escape sequence to query the horizontal cursor position
static
void
linenoiseAtExit
(
void
)
{
* and return it. On error -1 is returned, on success the position of the
disableRawMode
(
STDIN_FILENO
);
* cursor. */
freeHistory
();
static
int
getCursorPosition
(
int
ifd
,
int
ofd
)
{
char
buf
[
32
];
int
cols
,
rows
;
unsigned
int
i
=
0
;
/* Report cursor location */
if
(
write
(
ofd
,
"
\x1b
[6n"
,
4
)
!=
4
)
return
-
1
;
/* Read the response: ESC [ rows ; cols R */
while
(
i
<
sizeof
(
buf
)
-
1
)
{
if
(
read
(
ifd
,
buf
+
i
,
1
)
!=
1
)
break
;
if
(
buf
[
i
]
==
'R'
)
break
;
i
++
;
}
buf
[
i
]
=
'\0'
;
/* Parse it. */
if
(
buf
[
0
]
!=
ESC
||
buf
[
1
]
!=
'['
)
return
-
1
;
if
(
sscanf
(
buf
+
2
,
"%d;%d"
,
&
rows
,
&
cols
)
!=
2
)
return
-
1
;
return
cols
;
}
}
static
int
getColumns
(
void
)
{
/* Try to get the number of columns in the current terminal, or assume 80
* if it fails. */
static
int
getColumns
(
int
ifd
,
int
ofd
)
{
struct
winsize
ws
;
struct
winsize
ws
;
if
(
ioctl
(
1
,
TIOCGWINSZ
,
&
ws
)
==
-
1
)
return
80
;
if
(
ioctl
(
1
,
TIOCGWINSZ
,
&
ws
)
==
-
1
||
ws
.
ws_col
==
0
)
{
return
ws
.
ws_col
;
/* ioctl() failed. Try to query the terminal itself. */
}
int
start
,
cols
;
static
void
refreshLine
(
int
fd
,
const
char
*
prompt
,
char
*
buf
,
size_t
len
,
size_t
pos
,
size_t
cols
)
{
/* Get the initial position so we can restore it later. */
char
seq
[
64
];
start
=
getCursorPosition
(
ifd
,
ofd
);
size_t
plen
=
strlen
(
prompt
);
if
(
start
==
-
1
)
goto
failed
;
while
((
plen
+
pos
)
>=
cols
)
{
/* Go to right margin and get position. */
buf
++
;
if
(
write
(
ofd
,
"
\x1b
[999C"
,
6
)
!=
6
)
goto
failed
;
len
--
;
cols
=
getCursorPosition
(
ifd
,
ofd
);
pos
--
;
if
(
cols
==
-
1
)
goto
failed
;
}
while
(
plen
+
len
>
cols
)
{
/* Restore position. */
len
--
;
if
(
cols
>
start
)
{
char
seq
[
32
];
snprintf
(
seq
,
32
,
"
\x1b
[%dD"
,
cols
-
start
);
if
(
write
(
ofd
,
seq
,
strlen
(
seq
))
==
-
1
)
{
/* Can't recover... */
}
}
return
cols
;
}
else
{
return
ws
.
ws_col
;
}
}
/* Cursor to left edge */
failed:
snprintf
(
seq
,
64
,
"
\x1b
[0G"
);
return
80
;
if
(
write
(
fd
,
seq
,
strlen
(
seq
))
==
-
1
)
return
;
/* Write the prompt and the current buffer content */
if
(
write
(
fd
,
prompt
,
strlen
(
prompt
))
==
-
1
)
return
;
if
(
write
(
fd
,
buf
,
len
)
==
-
1
)
return
;
/* Erase to right */
snprintf
(
seq
,
64
,
"
\x1b
[0K"
);
if
(
write
(
fd
,
seq
,
strlen
(
seq
))
==
-
1
)
return
;
/* Move cursor to original position. */
snprintf
(
seq
,
64
,
"
\x1b
[0G
\x1b
[%dC"
,
(
int
)(
pos
+
plen
));
if
(
write
(
fd
,
seq
,
strlen
(
seq
))
==
-
1
)
return
;
}
}
static
void
beep
()
{
/* Clear the screen. Used to handle ctrl+l */
void
linenoiseClearScreen
(
void
)
{
if
(
write
(
STDOUT_FILENO
,
"
\x1b
[H
\x1b
[2J"
,
7
)
<=
0
)
{
/* nothing to do, just to avoid warning. */
}
}
/* Beep, used for completion when there is nothing to complete or when all
* the choices were already shown. */
static
void
linenoiseBeep
(
void
)
{
fprintf
(
stderr
,
"\x7"
);
fprintf
(
stderr
,
"\x7"
);
fflush
(
stderr
);
fflush
(
stderr
);
}
}
/* ============================== Completion ================================ */
/* Free a list of completion option populated by linenoiseAddCompletion(). */
static
void
freeCompletions
(
linenoiseCompletions
*
lc
)
{
static
void
freeCompletions
(
linenoiseCompletions
*
lc
)
{
size_t
i
;
size_t
i
;
for
(
i
=
0
;
i
<
lc
->
len
;
i
++
)
for
(
i
=
0
;
i
<
lc
->
len
;
i
++
)
...
@@ -226,28 +329,39 @@ static void freeCompletions(linenoiseCompletions *lc) {
...
@@ -226,28 +329,39 @@ static void freeCompletions(linenoiseCompletions *lc) {
free
(
lc
->
cvec
);
free
(
lc
->
cvec
);
}
}
static
int
completeLine
(
int
fd
,
const
char
*
prompt
,
char
*
buf
,
size_t
buflen
,
size_t
*
len
,
size_t
*
pos
,
size_t
cols
)
{
/* This is an helper function for linenoiseEdit() and is called when the
* user types the <tab> key in order to complete the string currently in the
* input.
*
* The state of the editing is encapsulated into the pointed linenoiseState
* structure as described in the structure definition. */
static
int
completeLine
(
struct
linenoiseState
*
ls
)
{
linenoiseCompletions
lc
=
{
0
,
NULL
};
linenoiseCompletions
lc
=
{
0
,
NULL
};
int
nread
,
nwritten
;
int
nread
,
nwritten
;
char
c
=
0
;
char
c
=
0
;
completionCallback
(
buf
,
&
lc
);
completionCallback
(
ls
->
buf
,
&
lc
);
if
(
lc
.
len
==
0
)
{
if
(
lc
.
len
==
0
)
{
b
eep
();
linenoiseB
eep
();
}
else
{
}
else
{
size_t
stop
=
0
,
i
=
0
;
size_t
stop
=
0
,
i
=
0
;
size_t
clen
;
while
(
!
stop
)
{
while
(
!
stop
)
{
/* Show completion or original buffer */
/* Show completion or original buffer */
if
(
i
<
lc
.
len
)
{
if
(
i
<
lc
.
len
)
{
clen
=
strlen
(
lc
.
cvec
[
i
]);
struct
linenoiseState
saved
=
*
ls
;
refreshLine
(
fd
,
prompt
,
lc
.
cvec
[
i
],
clen
,
clen
,
cols
);
ls
->
len
=
ls
->
pos
=
strlen
(
lc
.
cvec
[
i
]);
ls
->
buf
=
lc
.
cvec
[
i
];
refreshLine
(
ls
);
ls
->
len
=
saved
.
len
;
ls
->
pos
=
saved
.
pos
;
ls
->
buf
=
saved
.
buf
;
}
else
{
}
else
{
refreshLine
(
fd
,
prompt
,
buf
,
*
len
,
*
pos
,
co
ls
);
refreshLine
(
ls
);
}
}
nread
=
read
(
fd
,
&
c
,
1
);
nread
=
read
(
ls
->
i
fd
,
&
c
,
1
);
if
(
nread
<=
0
)
{
if
(
nread
<=
0
)
{
freeCompletions
(
&
lc
);
freeCompletions
(
&
lc
);
return
-
1
;
return
-
1
;
...
@@ -256,20 +370,18 @@ static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, si
...
@@ -256,20 +370,18 @@ static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, si
switch
(
c
)
{
switch
(
c
)
{
case
9
:
/* tab */
case
9
:
/* tab */
i
=
(
i
+
1
)
%
(
lc
.
len
+
1
);
i
=
(
i
+
1
)
%
(
lc
.
len
+
1
);
if
(
i
==
lc
.
len
)
b
eep
();
if
(
i
==
lc
.
len
)
linenoiseB
eep
();
break
;
break
;
case
27
:
/* escape */
case
27
:
/* escape */
/* Re-show original buffer */
/* Re-show original buffer */
if
(
i
<
lc
.
len
)
{
if
(
i
<
lc
.
len
)
refreshLine
(
ls
);
refreshLine
(
fd
,
prompt
,
buf
,
*
len
,
*
pos
,
cols
);
}
stop
=
1
;
stop
=
1
;
break
;
break
;
default:
default:
/* Update buffer and return */
/* Update buffer and return */
if
(
i
<
lc
.
len
)
{
if
(
i
<
lc
.
len
)
{
nwritten
=
snprintf
(
buf
,
buflen
,
"%s"
,
lc
.
cvec
[
i
]);
nwritten
=
snprintf
(
ls
->
buf
,
ls
->
buflen
,
"%s"
,
lc
.
cvec
[
i
]);
*
len
=
*
pos
=
nwritten
;
ls
->
len
=
ls
->
pos
=
nwritten
;
}
}
stop
=
1
;
stop
=
1
;
break
;
break
;
...
@@ -281,214 +393,526 @@ static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, si
...
@@ -281,214 +393,526 @@ static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, si
return
c
;
/* Return last read character */
return
c
;
/* Return last read character */
}
}
void
linenoiseClearScreen
(
void
)
{
/* Register a callback function to be called for tab-completion. */
if
(
write
(
STDIN_FILENO
,
"
\x1b
[H
\x1b
[2J"
,
7
)
<=
0
)
{
void
linenoiseSetCompletionCallback
(
linenoiseCompletionCallback
*
fn
)
{
/* nothing to do, just to avoid warning. */
completionCallback
=
fn
;
}
/* This function is used by the callback function registered by the user
* in order to add completion options given the input string when the
* user typed <tab>. See the example.c source code for a very easy to
* understand example. */
void
linenoiseAddCompletion
(
linenoiseCompletions
*
lc
,
const
char
*
str
)
{
size_t
len
=
strlen
(
str
);
char
*
copy
,
**
cvec
;
copy
=
malloc
(
len
+
1
);
if
(
copy
==
NULL
)
return
;
memcpy
(
copy
,
str
,
len
+
1
);
cvec
=
realloc
(
lc
->
cvec
,
sizeof
(
char
*
)
*
(
lc
->
len
+
1
));
if
(
cvec
==
NULL
)
{
free
(
copy
);
return
;
}
lc
->
cvec
=
cvec
;
lc
->
cvec
[
lc
->
len
++
]
=
copy
;
}
/* =========================== Line editing ================================= */
/* We define a very simple "append buffer" structure, that is an heap
* allocated string where we can append to. This is useful in order to
* write all the escape sequences in a buffer and flush them to the standard
* output in a single call, to avoid flickering effects. */
struct
abuf
{
char
*
b
;
int
len
;
};
static
void
abInit
(
struct
abuf
*
ab
)
{
ab
->
b
=
NULL
;
ab
->
len
=
0
;
}
static
void
abAppend
(
struct
abuf
*
ab
,
const
char
*
s
,
int
len
)
{
char
*
new
=
realloc
(
ab
->
b
,
ab
->
len
+
len
);
if
(
new
==
NULL
)
return
;
memcpy
(
new
+
ab
->
len
,
s
,
len
);
ab
->
b
=
new
;
ab
->
len
+=
len
;
}
static
void
abFree
(
struct
abuf
*
ab
)
{
free
(
ab
->
b
);
}
/* Single line low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content,
* cursor position, and number of columns of the terminal. */
static
void
refreshSingleLine
(
struct
linenoiseState
*
l
)
{
char
seq
[
64
];
size_t
plen
=
strlen
(
l
->
prompt
);
int
fd
=
l
->
ofd
;
char
*
buf
=
l
->
buf
;
size_t
len
=
l
->
len
;
size_t
pos
=
l
->
pos
;
struct
abuf
ab
;
while
((
plen
+
pos
)
>=
l
->
cols
)
{
buf
++
;
len
--
;
pos
--
;
}
while
(
plen
+
len
>
l
->
cols
)
{
len
--
;
}
abInit
(
&
ab
);
/* Cursor to left edge */
snprintf
(
seq
,
64
,
"
\x1b
[0G"
);
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
/* Write the prompt and the current buffer content */
abAppend
(
&
ab
,
l
->
prompt
,
strlen
(
l
->
prompt
));
abAppend
(
&
ab
,
buf
,
len
);
/* Erase to right */
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
));
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
if
(
write
(
fd
,
ab
.
b
,
ab
.
len
)
==
-
1
)
{}
/* Can't recover from write error. */
abFree
(
&
ab
);
}
/* Multi line low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content,
* cursor position, and number of columns of the terminal. */
static
void
refreshMultiLine
(
struct
linenoiseState
*
l
)
{
char
seq
[
64
];
int
plen
=
strlen
(
l
->
prompt
);
int
rows
=
(
plen
+
l
->
len
+
l
->
cols
-
1
)
/
l
->
cols
;
/* rows used by current buf. */
int
rpos
=
(
plen
+
l
->
oldpos
+
l
->
cols
)
/
l
->
cols
;
/* cursor relative row. */
int
rpos2
;
/* rpos after refresh. */
int
old_rows
=
l
->
maxrows
;
int
fd
=
l
->
ofd
,
j
;
struct
abuf
ab
;
/* Update maxrows if needed. */
if
(
rows
>
(
int
)
l
->
maxrows
)
l
->
maxrows
=
rows
;
/* First step: clear all the lines used before. To do so start by
* going to the last row. */
abInit
(
&
ab
);
if
(
old_rows
-
rpos
>
0
)
{
lndebug
(
"go down %d"
,
old_rows
-
rpos
);
snprintf
(
seq
,
64
,
"
\x1b
[%dB"
,
old_rows
-
rpos
);
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
}
/* 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"
);
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
}
/* Clean the top line. */
lndebug
(
"clear"
);
snprintf
(
seq
,
64
,
"
\x1b
[0G
\x1b
[0K"
);
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
/* Write the prompt and the current buffer content */
abAppend
(
&
ab
,
l
->
prompt
,
strlen
(
l
->
prompt
));
abAppend
(
&
ab
,
l
->
buf
,
l
->
len
);
/* If we are at the very end of the screen with our prompt, we need to
* emit a newline and move the prompt to the first column. */
if
(
l
->
pos
&&
l
->
pos
==
l
->
len
&&
(
l
->
pos
+
plen
)
%
l
->
cols
==
0
)
{
lndebug
(
"<newline>"
);
abAppend
(
&
ab
,
"
\n
"
,
1
);
snprintf
(
seq
,
64
,
"
\x1b
[0G"
);
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
rows
++
;
if
(
rows
>
(
int
)
l
->
maxrows
)
l
->
maxrows
=
rows
;
}
/* Move cursor to right position. */
rpos2
=
(
plen
+
l
->
pos
+
l
->
cols
)
/
l
->
cols
;
/* current cursor relative row. */
lndebug
(
"rpos2 %d"
,
rpos2
);
/* Go up till we reach the expected positon. */
if
(
rows
-
rpos2
>
0
)
{
lndebug
(
"go-up %d"
,
rows
-
rpos2
);
snprintf
(
seq
,
64
,
"
\x1b
[%dA"
,
rows
-
rpos2
);
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
}
/* 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
));
abAppend
(
&
ab
,
seq
,
strlen
(
seq
));
lndebug
(
"
\n
"
);
l
->
oldpos
=
l
->
pos
;
if
(
write
(
fd
,
ab
.
b
,
ab
.
len
)
==
-
1
)
{}
/* Can't recover from write error. */
abFree
(
&
ab
);
}
/* Calls the two low level functions refreshSingleLine() or
* refreshMultiLine() according to the selected mode. */
static
void
refreshLine
(
struct
linenoiseState
*
l
)
{
if
(
mlmode
)
refreshMultiLine
(
l
);
else
refreshSingleLine
(
l
);
}
/* Insert the character 'c' at cursor current position.
*
* On error writing to the terminal -1 is returned, otherwise 0. */
int
linenoiseEditInsert
(
struct
linenoiseState
*
l
,
char
c
)
{
if
(
l
->
len
<
l
->
buflen
)
{
if
(
l
->
len
==
l
->
pos
)
{
l
->
buf
[
l
->
pos
]
=
c
;
l
->
pos
++
;
l
->
len
++
;
l
->
buf
[
l
->
len
]
=
'\0'
;
if
((
!
mlmode
&&
l
->
plen
+
l
->
len
<
l
->
cols
)
/* || mlmode */
)
{
/* Avoid a full update of the line in the
* trivial case. */
if
(
write
(
l
->
ofd
,
&
c
,
1
)
==
-
1
)
return
-
1
;
}
else
{
refreshLine
(
l
);
}
}
else
{
memmove
(
l
->
buf
+
l
->
pos
+
1
,
l
->
buf
+
l
->
pos
,
l
->
len
-
l
->
pos
);
l
->
buf
[
l
->
pos
]
=
c
;
l
->
len
++
;
l
->
pos
++
;
l
->
buf
[
l
->
len
]
=
'\0'
;
refreshLine
(
l
);
}
}
return
0
;
}
/* Move cursor on the left. */
void
linenoiseEditMoveLeft
(
struct
linenoiseState
*
l
)
{
if
(
l
->
pos
>
0
)
{
l
->
pos
--
;
refreshLine
(
l
);
}
}
/* Move cursor on the right. */
void
linenoiseEditMoveRight
(
struct
linenoiseState
*
l
)
{
if
(
l
->
pos
!=
l
->
len
)
{
l
->
pos
++
;
refreshLine
(
l
);
}
}
/* Move cursor to the start of the line. */
void
linenoiseEditMoveHome
(
struct
linenoiseState
*
l
)
{
if
(
l
->
pos
!=
0
)
{
l
->
pos
=
0
;
refreshLine
(
l
);
}
}
/* Move cursor to the end of the line. */
void
linenoiseEditMoveEnd
(
struct
linenoiseState
*
l
)
{
if
(
l
->
pos
!=
l
->
len
)
{
l
->
pos
=
l
->
len
;
refreshLine
(
l
);
}
}
/* Substitute the currently edited line with the next or previous history
* entry as specified by 'dir'. */
#define LINENOISE_HISTORY_NEXT 0
#define LINENOISE_HISTORY_PREV 1
void
linenoiseEditHistoryNext
(
struct
linenoiseState
*
l
,
int
dir
)
{
if
(
history_len
>
1
)
{
/* Update the current history entry before to
* overwrite it with the next one. */
free
(
history
[
history_len
-
1
-
l
->
history_index
]);
history
[
history_len
-
1
-
l
->
history_index
]
=
strdup
(
l
->
buf
);
/* Show the new entry */
l
->
history_index
+=
(
dir
==
LINENOISE_HISTORY_PREV
)
?
1
:
-
1
;
if
(
l
->
history_index
<
0
)
{
l
->
history_index
=
0
;
return
;
}
else
if
(
l
->
history_index
>=
history_len
)
{
l
->
history_index
=
history_len
-
1
;
return
;
}
strncpy
(
l
->
buf
,
history
[
history_len
-
1
-
l
->
history_index
],
l
->
buflen
);
l
->
buf
[
l
->
buflen
-
1
]
=
'\0'
;
l
->
len
=
l
->
pos
=
strlen
(
l
->
buf
);
refreshLine
(
l
);
}
}
/* Delete the character at the right of the cursor without altering the cursor
* position. Basically this is what happens with the "Delete" keyboard key. */
void
linenoiseEditDelete
(
struct
linenoiseState
*
l
)
{
if
(
l
->
len
>
0
&&
l
->
pos
<
l
->
len
)
{
memmove
(
l
->
buf
+
l
->
pos
,
l
->
buf
+
l
->
pos
+
1
,
l
->
len
-
l
->
pos
-
1
);
l
->
len
--
;
l
->
buf
[
l
->
len
]
=
'\0'
;
refreshLine
(
l
);
}
}
/* Backspace implementation. */
void
linenoiseEditBackspace
(
struct
linenoiseState
*
l
)
{
if
(
l
->
pos
>
0
&&
l
->
len
>
0
)
{
memmove
(
l
->
buf
+
l
->
pos
-
1
,
l
->
buf
+
l
->
pos
,
l
->
len
-
l
->
pos
);
l
->
pos
--
;
l
->
len
--
;
l
->
buf
[
l
->
len
]
=
'\0'
;
refreshLine
(
l
);
}
}
}
}
static
int
linenoisePrompt
(
int
fd
,
char
*
buf
,
size_t
buflen
,
const
char
*
prompt
)
{
/* Delete the previosu word, maintaining the cursor at the start of the
size_t
plen
=
strlen
(
prompt
);
* current word. */
size_t
pos
=
0
;
void
linenoiseEditDeletePrevWord
(
struct
linenoiseState
*
l
)
{
size_t
len
=
0
;
size_t
old_pos
=
l
->
pos
;
size_t
cols
=
getColumns
();
int
history_index
=
0
;
size_t
old_pos
;
size_t
diff
;
size_t
diff
;
buf
[
0
]
=
'\0'
;
while
(
l
->
pos
>
0
&&
l
->
buf
[
l
->
pos
-
1
]
==
' '
)
buflen
--
;
/* Make sure there is always space for the nulterm */
l
->
pos
--
;
while
(
l
->
pos
>
0
&&
l
->
buf
[
l
->
pos
-
1
]
!=
' '
)
l
->
pos
--
;
diff
=
old_pos
-
l
->
pos
;
memmove
(
l
->
buf
+
l
->
pos
,
l
->
buf
+
old_pos
,
l
->
len
-
old_pos
+
1
);
l
->
len
-=
diff
;
refreshLine
(
l
);
}
/* This function is the core of the line editing capability of linenoise.
* It expects 'fd' to be already in "raw mode" so that every key pressed
* will be returned ASAP to read().
*
* The resulting string is put into 'buf' when the user type enter, or
* when ctrl+d is typed.
*
* The function returns the length of the current buffer. */
static
int
linenoiseEdit
(
int
stdin_fd
,
int
stdout_fd
,
char
*
buf
,
size_t
buflen
,
const
char
*
prompt
)
{
struct
linenoiseState
l
;
/* Populate the linenoise state that we pass to functions implementing
* specific editing functionalities. */
l
.
ifd
=
stdin_fd
;
l
.
ofd
=
stdout_fd
;
l
.
buf
=
buf
;
l
.
buflen
=
buflen
;
l
.
prompt
=
prompt
;
l
.
plen
=
strlen
(
prompt
);
l
.
oldpos
=
l
.
pos
=
0
;
l
.
len
=
0
;
l
.
cols
=
getColumns
(
stdin_fd
,
stdout_fd
);
l
.
maxrows
=
0
;
l
.
history_index
=
0
;
/* Buffer starts empty. */
l
.
buf
[
0
]
=
'\0'
;
l
.
buflen
--
;
/* Make sure there is always space for the nulterm */
/* The latest history entry is always our current buffer, that
/* The latest history entry is always our current buffer, that
* initially is just an empty string. */
* initially is just an empty string. */
linenoiseHistoryAdd
(
""
);
linenoiseHistoryAdd
(
""
);
if
(
write
(
fd
,
prompt
,
plen
)
==
-
1
)
return
-
1
;
if
(
write
(
l
.
o
fd
,
prompt
,
l
.
plen
)
==
-
1
)
return
-
1
;
while
(
1
)
{
while
(
1
)
{
char
c
;
char
c
;
int
nread
;
int
nread
;
char
seq
[
2
],
seq2
[
2
];
char
seq
[
3
];
nread
=
read
(
fd
,
&
c
,
1
);
nread
=
read
(
l
.
i
fd
,
&
c
,
1
);
if
(
nread
<=
0
)
return
len
;
if
(
nread
<=
0
)
return
l
.
len
;
/* Only autocomplete when the callback is set. It returns < 0 when
/* Only autocomplete when the callback is set. It returns < 0 when
* there was an error reading from fd. Otherwise it will return the
* there was an error reading from fd. Otherwise it will return the
* character that should be handled next. */
* character that should be handled next. */
if
(
c
==
9
&&
completionCallback
!=
NULL
)
{
if
(
c
==
9
&&
completionCallback
!=
NULL
)
{
c
=
completeLine
(
fd
,
prompt
,
buf
,
buflen
,
&
len
,
&
pos
,
cols
);
c
=
completeLine
(
&
l
);
/* Return on errors */
/* Return on errors */
if
(
c
<
0
)
return
len
;
if
(
c
<
0
)
return
l
.
len
;
/* Read next character when 0 */
/* Read next character when 0 */
if
(
c
==
0
)
continue
;
if
(
c
==
0
)
continue
;
}
}
switch
(
c
)
{
switch
(
c
)
{
case
13
:
/* enter */
case
ENTER
:
/* enter */
history_len
--
;
history_len
--
;
free
(
history
[
history_len
]);
free
(
history
[
history_len
]);
return
(
int
)
len
;
return
(
int
)
l
.
len
;
case
3
:
/* ctrl-c */
case
CTRL_C
:
/* ctrl-c */
errno
=
EAGAIN
;
errno
=
EAGAIN
;
return
-
1
;
return
-
1
;
case
127
:
/* backspace */
case
BACKSPACE
:
/* backspace */
case
8
:
/* ctrl-h */
case
8
:
/* ctrl-h */
if
(
pos
>
0
&&
len
>
0
)
{
linenoiseEditBackspace
(
&
l
);
memmove
(
buf
+
pos
-
1
,
buf
+
pos
,
len
-
pos
);
pos
--
;
len
--
;
buf
[
len
]
=
'\0'
;
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
}
break
;
break
;
case
4
:
/* ctrl-d, remove char at right of cursor */
case
CTRL_D
:
/* ctrl-d, remove char at right of cursor, or of the
if
(
len
>
1
&&
pos
<
(
len
-
1
))
{
line is empty, act as end-of-file. */
memmove
(
buf
+
pos
,
buf
+
pos
+
1
,
len
-
pos
);
if
(
l
.
len
>
0
)
{
len
--
;
linenoiseEditDelete
(
&
l
);
buf
[
len
]
=
'\0'
;
}
else
{
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
}
else
if
(
len
==
0
)
{
history_len
--
;
history_len
--
;
free
(
history
[
history_len
]);
free
(
history
[
history_len
]);
return
-
1
;
return
-
1
;
}
}
break
;
break
;
case
20
:
/* ctrl-t */
case
CTRL_T
:
/* ctrl-t
, swaps current character with previous.
*/
if
(
pos
>
0
&&
pos
<
len
)
{
if
(
l
.
pos
>
0
&&
l
.
pos
<
l
.
len
)
{
int
aux
=
buf
[
pos
-
1
];
int
aux
=
buf
[
l
.
pos
-
1
];
buf
[
pos
-
1
]
=
buf
[
pos
];
buf
[
l
.
pos
-
1
]
=
buf
[
l
.
pos
];
buf
[
pos
]
=
aux
;
buf
[
l
.
pos
]
=
aux
;
if
(
pos
!=
len
-
1
)
pos
++
;
if
(
l
.
pos
!=
l
.
len
-
1
)
l
.
pos
++
;
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
refreshLine
(
&
l
);
}
}
break
;
break
;
case
2
:
/* ctrl-b */
case
CTRL_B
:
/* ctrl-b */
goto
left_arrow
;
linenoiseEditMoveLeft
(
&
l
);
case
6
:
/* ctrl-f */
goto
right_arrow
;
case
16
:
/* ctrl-p */
seq
[
1
]
=
65
;
goto
up_down_arrow
;
case
14
:
/* ctrl-n */
seq
[
1
]
=
66
;
goto
up_down_arrow
;
break
;
break
;
case
27
:
/* escape sequence */
case
CTRL_F
:
/* ctrl-f */
if
(
read
(
fd
,
seq
,
2
)
==
-
1
)
break
;
linenoiseEditMoveRight
(
&
l
);
if
(
seq
[
0
]
==
91
&&
seq
[
1
]
==
68
)
{
break
;
left_arrow:
case
CTRL_P
:
/* ctrl-p */
/* left arrow */
linenoiseEditHistoryNext
(
&
l
,
LINENOISE_HISTORY_PREV
);
if
(
pos
>
0
)
{
break
;
pos
--
;
case
CTRL_N
:
/* ctrl-n */
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
linenoiseEditHistoryNext
(
&
l
,
LINENOISE_HISTORY_NEXT
);
}
break
;
}
else
if
(
seq
[
0
]
==
91
&&
seq
[
1
]
==
67
)
{
case
ESC
:
/* escape sequence */
right_arrow:
/* Read the next two bytes representing the escape sequence.
/* right arrow */
* Use two calls to handle slow terminals returning the two
if
(
pos
!=
len
)
{
* chars at different times. */
pos
++
;
if
(
read
(
l
.
ifd
,
seq
,
1
)
==
-
1
)
break
;
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
if
(
read
(
l
.
ifd
,
seq
+
1
,
1
)
==
-
1
)
break
;
}
}
else
if
(
seq
[
0
]
==
91
&&
(
seq
[
1
]
==
65
||
seq
[
1
]
==
66
))
{
/* ESC [ sequences. */
up_down_arrow:
if
(
seq
[
0
]
==
'['
)
{
/* up and down arrow: history */
if
(
seq
[
1
]
>=
'0'
&&
seq
[
1
]
<=
'9'
)
{
if
(
history_len
>
1
)
{
/* Extended escape, read additional byte. */
/* Update the current history entry before to
if
(
read
(
l
.
ifd
,
seq
+
2
,
1
)
==
-
1
)
break
;
* overwrite it with tne next one. */
if
(
seq
[
2
]
==
'~'
)
{
free
(
history
[
history_len
-
1
-
history_index
]);
switch
(
seq
[
1
])
{
history
[
history_len
-
1
-
history_index
]
=
strdup
(
buf
);
case
'3'
:
/* Delete key. */
/* Show the new entry */
linenoiseEditDelete
(
&
l
);
history_index
+=
(
seq
[
1
]
==
65
)
?
1
:
-
1
;
break
;
if
(
history_index
<
0
)
{
}
history_index
=
0
;
}
}
else
{
switch
(
seq
[
1
])
{
case
'A'
:
/* Up */
linenoiseEditHistoryNext
(
&
l
,
LINENOISE_HISTORY_PREV
);
break
;
case
'B'
:
/* Down */
linenoiseEditHistoryNext
(
&
l
,
LINENOISE_HISTORY_NEXT
);
break
;
case
'C'
:
/* Right */
linenoiseEditMoveRight
(
&
l
);
break
;
case
'D'
:
/* Left */
linenoiseEditMoveLeft
(
&
l
);
break
;
case
'H'
:
/* Home */
linenoiseEditMoveHome
(
&
l
);
break
;
break
;
}
else
if
(
history_index
>=
history_len
)
{
case
'F'
:
/* End*/
history_index
=
history_len
-
1
;
linenoiseEditMoveEnd
(
&
l
)
;
break
;
break
;
}
}
strncpy
(
buf
,
history
[
history_len
-
1
-
history_index
],
buflen
);
buf
[
buflen
]
=
'\0'
;
len
=
pos
=
strlen
(
buf
);
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
;
/* ESC O sequences. */
if
(
seq
[
1
]
==
51
&&
seq2
[
0
]
==
126
)
{
else
if
(
seq
[
0
]
==
'O'
)
{
/* delete */
switch
(
seq
[
1
])
{
if
(
len
>
0
&&
pos
<
len
)
{
case
'H'
:
/* Home */
memmove
(
buf
+
pos
,
buf
+
pos
+
1
,
len
-
pos
-
1
);
linenoiseEditMoveHome
(
&
l
);
len
--
;
break
;
buf
[
len
]
=
'\0'
;
case
'F'
:
/* End*/
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
linenoiseEditMoveEnd
(
&
l
);
}
break
;
}
}
}
}
break
;
break
;
default:
default:
if
(
len
<
buflen
)
{
if
(
linenoiseEditInsert
(
&
l
,
c
))
return
-
1
;
if
(
len
==
pos
)
{
buf
[
pos
]
=
c
;
pos
++
;
len
++
;
buf
[
len
]
=
'\0'
;
if
(
plen
+
len
<
cols
)
{
/* Avoid a full update of the line in the
* trivial case. */
if
(
write
(
fd
,
&
c
,
1
)
==
-
1
)
return
-
1
;
}
else
{
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
}
}
else
{
memmove
(
buf
+
pos
+
1
,
buf
+
pos
,
len
-
pos
);
buf
[
pos
]
=
c
;
len
++
;
pos
++
;
buf
[
len
]
=
'\0'
;
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
}
}
break
;
break
;
case
21
:
/* Ctrl+u, delete the whole line. */
case
CTRL_U
:
/* Ctrl+u, delete the whole line. */
buf
[
0
]
=
'\0'
;
buf
[
0
]
=
'\0'
;
pos
=
len
=
0
;
l
.
pos
=
l
.
len
=
0
;
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
refreshLine
(
&
l
);
break
;
break
;
case
11
:
/* Ctrl+k, delete from current to end of line. */
case
CTRL_K
:
/* Ctrl+k, delete from current to end of line. */
buf
[
pos
]
=
'\0'
;
buf
[
l
.
pos
]
=
'\0'
;
len
=
pos
;
l
.
len
=
l
.
pos
;
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
refreshLine
(
&
l
);
break
;
break
;
case
1
:
/* Ctrl+a, go to the start of the line */
case
CTRL_A
:
/* Ctrl+a, go to the start of the line */
pos
=
0
;
linenoiseEditMoveHome
(
&
l
);
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
break
;
break
;
case
5
:
/* ctrl+e, go to the end of the line */
case
CTRL_E
:
/* ctrl+e, go to the end of the line */
pos
=
len
;
linenoiseEditMoveEnd
(
&
l
);
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
break
;
break
;
case
12
:
/* ctrl+l, clear screen */
case
CTRL_L
:
/* ctrl+l, clear screen */
linenoiseClearScreen
();
linenoiseClearScreen
();
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
refreshLine
(
&
l
);
break
;
break
;
case
23
:
/* ctrl+w, delete previous word */
case
CTRL_W
:
/* ctrl+w, delete previous word */
old_pos
=
pos
;
linenoiseEditDeletePrevWord
(
&
l
);
while
(
pos
>
0
&&
buf
[
pos
-
1
]
==
' '
)
pos
--
;
while
(
pos
>
0
&&
buf
[
pos
-
1
]
!=
' '
)
pos
--
;
diff
=
old_pos
-
pos
;
memmove
(
&
buf
[
pos
],
&
buf
[
old_pos
],
len
-
old_pos
+
1
);
len
-=
diff
;
refreshLine
(
fd
,
prompt
,
buf
,
len
,
pos
,
cols
);
break
;
break
;
}
}
}
}
return
len
;
return
l
.
len
;
}
/* This special mode is used by linenoise in order to print scan codes
* on screen for debugging / development purposes. It is implemented
* by the linenoise_example program using the --keycodes option. */
void
linenoisePrintKeyCodes
(
void
)
{
char
quit
[
4
];
printf
(
"Linenoise key codes debugging mode.
\n
"
"Press keys to see scan codes. Type 'quit' at any time to exit.
\n
"
);
if
(
enableRawMode
(
STDIN_FILENO
)
==
-
1
)
return
;
memset
(
quit
,
' '
,
4
);
while
(
1
)
{
char
c
;
int
nread
;
nread
=
read
(
STDIN_FILENO
,
&
c
,
1
);
if
(
nread
<=
0
)
continue
;
memmove
(
quit
,
quit
+
1
,
sizeof
(
quit
)
-
1
);
/* shift string to left. */
quit
[
sizeof
(
quit
)
-
1
]
=
c
;
/* Insert current char on the right. */
if
(
memcmp
(
quit
,
"quit"
,
sizeof
(
quit
))
==
0
)
break
;
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. */
fflush
(
stdout
);
}
disableRawMode
(
STDIN_FILENO
);
}
}
/* This function calls the line editing function linenoiseEdit() using
* the STDIN file descriptor set in raw mode. */
static
int
linenoiseRaw
(
char
*
buf
,
size_t
buflen
,
const
char
*
prompt
)
{
static
int
linenoiseRaw
(
char
*
buf
,
size_t
buflen
,
const
char
*
prompt
)
{
int
fd
=
STDIN_FILENO
;
int
count
;
int
count
;
if
(
buflen
==
0
)
{
if
(
buflen
==
0
)
{
...
@@ -496,6 +920,7 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
...
@@ -496,6 +920,7 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
return
-
1
;
return
-
1
;
}
}
if
(
!
isatty
(
STDIN_FILENO
))
{
if
(
!
isatty
(
STDIN_FILENO
))
{
/* Not a tty: read from file / pipe. */
if
(
fgets
(
buf
,
buflen
,
stdin
)
==
NULL
)
return
-
1
;
if
(
fgets
(
buf
,
buflen
,
stdin
)
==
NULL
)
return
-
1
;
count
=
strlen
(
buf
);
count
=
strlen
(
buf
);
if
(
count
&&
buf
[
count
-
1
]
==
'\n'
)
{
if
(
count
&&
buf
[
count
-
1
]
==
'\n'
)
{
...
@@ -503,14 +928,20 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
...
@@ -503,14 +928,20 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
buf
[
count
]
=
'\0'
;
buf
[
count
]
=
'\0'
;
}
}
}
else
{
}
else
{
if
(
enableRawMode
(
fd
)
==
-
1
)
return
-
1
;
/* Interactive editing. */
count
=
linenoisePrompt
(
fd
,
buf
,
buflen
,
prompt
);
if
(
enableRawMode
(
STDIN_FILENO
)
==
-
1
)
return
-
1
;
disableRawMode
(
fd
);
count
=
linenoiseEdit
(
STDIN_FILENO
,
STDOUT_FILENO
,
buf
,
buflen
,
prompt
);
disableRawMode
(
STDIN_FILENO
);
printf
(
"
\n
"
);
printf
(
"
\n
"
);
}
}
return
count
;
return
count
;
}
}
/* The high level function that is the main API of the linenoise library.
* This function checks if the terminal has basic capabilities, just checking
* for a blacklist of stupid terminals, and later either calls the line
* editing function or uses dummy fgets() so that you will be able to type
* something even in the most desperate of the conditions. */
char
*
linenoise
(
const
char
*
prompt
)
{
char
*
linenoise
(
const
char
*
prompt
)
{
char
buf
[
LINENOISE_MAX_LINE
];
char
buf
[
LINENOISE_MAX_LINE
];
int
count
;
int
count
;
...
@@ -534,29 +965,50 @@ char *linenoise(const char *prompt) {
...
@@ -534,29 +965,50 @@ char *linenoise(const char *prompt) {
}
}
}
}
/* Register a callback function to be called for tab-completion. */
/* ================================ History ================================= */
void
linenoiseSetCompletionCallback
(
linenoiseCompletionCallback
*
fn
)
{
completionCallback
=
fn
;
/* Free the history, but does not reset it. Only used when we have to
* exit() to avoid memory leaks are reported by valgrind & co. */
static
void
freeHistory
(
void
)
{
if
(
history
)
{
int
j
;
for
(
j
=
0
;
j
<
history_len
;
j
++
)
free
(
history
[
j
]);
free
(
history
);
}
}
}
void
linenoiseAddCompletion
(
linenoiseCompletions
*
lc
,
char
*
str
)
{
/* At exit we'll try to fix the terminal to the initial conditions. */
size_t
len
=
strlen
(
str
);
static
void
linenoiseAtExit
(
void
)
{
char
*
copy
=
malloc
(
len
+
1
);
disableRawMode
(
STDIN_FILENO
);
memcpy
(
copy
,
str
,
len
+
1
);
freeHistory
();
lc
->
cvec
=
realloc
(
lc
->
cvec
,
sizeof
(
char
*
)
*
(
lc
->
len
+
1
));
lc
->
cvec
[
lc
->
len
++
]
=
copy
;
}
}
/* Using a circular buffer is smarter, but a bit more complex to handle. */
/* This is the API call to add a new entry in the linenoise history.
* It uses a fixed array of char pointers that are shifted (memmoved)
* when the history max length is reached in order to remove the older
* entry and make room for the new one, so it is not exactly suitable for huge
* histories, but will work well for a few hundred of entries.
*
* Using a circular buffer is smarter, but a bit more complex to handle. */
int
linenoiseHistoryAdd
(
const
char
*
line
)
{
int
linenoiseHistoryAdd
(
const
char
*
line
)
{
char
*
linecopy
;
char
*
linecopy
;
if
(
history_max_len
==
0
)
return
0
;
if
(
history_max_len
==
0
)
return
0
;
/* Initialization on first call. */
if
(
history
==
NULL
)
{
if
(
history
==
NULL
)
{
history
=
malloc
(
sizeof
(
char
*
)
*
history_max_len
);
history
=
malloc
(
sizeof
(
char
*
)
*
history_max_len
);
if
(
history
==
NULL
)
return
0
;
if
(
history
==
NULL
)
return
0
;
memset
(
history
,
0
,(
sizeof
(
char
*
)
*
history_max_len
));
memset
(
history
,
0
,(
sizeof
(
char
*
)
*
history_max_len
));
}
}
/* Don't add duplicated lines. */
if
(
history_len
&&
!
strcmp
(
history
[
history_len
-
1
],
line
))
return
0
;
/* Add an heap allocated copy of the line in the history.
* If we reached the max length, remove the older line. */
linecopy
=
strdup
(
line
);
linecopy
=
strdup
(
line
);
if
(
!
linecopy
)
return
0
;
if
(
!
linecopy
)
return
0
;
if
(
history_len
==
history_max_len
)
{
if
(
history_len
==
history_max_len
)
{
...
@@ -569,6 +1021,10 @@ int linenoiseHistoryAdd(const char *line) {
...
@@ -569,6 +1021,10 @@ int linenoiseHistoryAdd(const char *line) {
return
1
;
return
1
;
}
}
/* Set the maximum length for the history. This function can be called even
* if there is already some history, the function will make sure to retain
* just the latest 'len' elements if the new history length value is smaller
* than the amount of items already inside the history. */
int
linenoiseHistorySetMaxLen
(
int
len
)
{
int
linenoiseHistorySetMaxLen
(
int
len
)
{
char
**
new
;
char
**
new
;
...
@@ -578,8 +1034,16 @@ int linenoiseHistorySetMaxLen(int len) {
...
@@ -578,8 +1034,16 @@ int linenoiseHistorySetMaxLen(int len) {
new
=
malloc
(
sizeof
(
char
*
)
*
len
);
new
=
malloc
(
sizeof
(
char
*
)
*
len
);
if
(
new
==
NULL
)
return
0
;
if
(
new
==
NULL
)
return
0
;
if
(
len
<
tocopy
)
tocopy
=
len
;
memcpy
(
new
,
history
+
(
history_max_len
-
tocopy
),
sizeof
(
char
*
)
*
tocopy
);
/* If we can't copy everything, free the elements we'll not use. */
if
(
len
<
tocopy
)
{
int
j
;
for
(
j
=
0
;
j
<
tocopy
-
len
;
j
++
)
free
(
history
[
j
]);
tocopy
=
len
;
}
memset
(
new
,
0
,
sizeof
(
char
*
)
*
len
);
memcpy
(
new
,
history
+
(
history_len
-
tocopy
),
sizeof
(
char
*
)
*
tocopy
);
free
(
history
);
free
(
history
);
history
=
new
;
history
=
new
;
}
}
...
@@ -591,7 +1055,7 @@ int linenoiseHistorySetMaxLen(int len) {
...
@@ -591,7 +1055,7 @@ int linenoiseHistorySetMaxLen(int len) {
/* Save the history in the specified file. On success 0 is returned
/* Save the history in the specified file. On success 0 is returned
* otherwise -1 is returned. */
* otherwise -1 is returned. */
int
linenoiseHistorySave
(
char
*
filename
)
{
int
linenoiseHistorySave
(
const
char
*
filename
)
{
FILE
*
fp
=
fopen
(
filename
,
"w"
);
FILE
*
fp
=
fopen
(
filename
,
"w"
);
int
j
;
int
j
;
...
@@ -607,7 +1071,7 @@ int linenoiseHistorySave(char *filename) {
...
@@ -607,7 +1071,7 @@ int linenoiseHistorySave(char *filename) {
*
*
* If the file exists and the operation succeeded 0 is returned, otherwise
* If the file exists and the operation succeeded 0 is returned, otherwise
* on error -1 is returned. */
* on error -1 is returned. */
int
linenoiseHistoryLoad
(
char
*
filename
)
{
int
linenoiseHistoryLoad
(
const
char
*
filename
)
{
FILE
*
fp
=
fopen
(
filename
,
"r"
);
FILE
*
fp
=
fopen
(
filename
,
"r"
);
char
buf
[
LINENOISE_MAX_LINE
];
char
buf
[
LINENOISE_MAX_LINE
];
...
...
deps/linenoise/linenoise.h
View file @
37031126
...
@@ -3,39 +3,44 @@
...
@@ -3,39 +3,44 @@
*
*
* See linenoise.c for more information.
* See linenoise.c for more information.
*
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
*
* All rights reserved.
* All rights reserved.
*
*
* Redistribution and use in source and binary forms, with or without
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
*
* * Redistributions of source code must retain the above copyright notice,
* * Redistributions in binary form must reproduce the above copyright
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
*
* to endorse or promote products derived from this software without
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* specific prior written permission.
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 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 __LINENOISE_H
#ifndef __LINENOISE_H
#define __LINENOISE_H
#define __LINENOISE_H
#ifdef __cplusplus
extern
"C"
{
#endif
typedef
struct
linenoiseCompletions
{
typedef
struct
linenoiseCompletions
{
size_t
len
;
size_t
len
;
char
**
cvec
;
char
**
cvec
;
...
@@ -43,13 +48,19 @@ typedef struct linenoiseCompletions {
...
@@ -43,13 +48,19 @@ typedef struct linenoiseCompletions {
typedef
void
(
linenoiseCompletionCallback
)(
const
char
*
,
linenoiseCompletions
*
);
typedef
void
(
linenoiseCompletionCallback
)(
const
char
*
,
linenoiseCompletions
*
);
void
linenoiseSetCompletionCallback
(
linenoiseCompletionCallback
*
);
void
linenoiseSetCompletionCallback
(
linenoiseCompletionCallback
*
);
void
linenoiseAddCompletion
(
linenoiseCompletions
*
,
char
*
);
void
linenoiseAddCompletion
(
linenoiseCompletions
*
,
const
char
*
);
char
*
linenoise
(
const
char
*
prompt
);
char
*
linenoise
(
const
char
*
prompt
);
int
linenoiseHistoryAdd
(
const
char
*
line
);
int
linenoiseHistoryAdd
(
const
char
*
line
);
int
linenoiseHistorySetMaxLen
(
int
len
);
int
linenoiseHistorySetMaxLen
(
int
len
);
int
linenoiseHistorySave
(
char
*
filename
);
int
linenoiseHistorySave
(
const
char
*
filename
);
int
linenoiseHistoryLoad
(
char
*
filename
);
int
linenoiseHistoryLoad
(
const
char
*
filename
);
void
linenoiseClearScreen
(
void
);
void
linenoiseClearScreen
(
void
);
void
linenoiseSetMultiLine
(
int
ml
);
void
linenoisePrintKeyCodes
(
void
);
#ifdef __cplusplus
}
#endif
#endif
/* __LINENOISE_H */
#endif
/* __LINENOISE_H */
src/redis-cli.c
View file @
37031126
...
@@ -865,6 +865,7 @@ static void repl() {
...
@@ -865,6 +865,7 @@ static void repl() {
sds
*
argv
;
sds
*
argv
;
config
.
interactive
=
1
;
config
.
interactive
=
1
;
linenoiseSetMultiLine
(
1
);
linenoiseSetCompletionCallback
(
completionCallback
);
linenoiseSetCompletionCallback
(
completionCallback
);
/* Only use history when stdin is a tty. */
/* Only use history when stdin is a tty. */
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment