2025-02-05 08:00:08 +01:00
/* linenoise.c -- VERSION 1.0
*
* Guerrilla line editing library against the idea that a line editing lib
* needs to be 20 , 000 lines of C code .
*
* You can find the latest source code at :
*
* http : //github.com/antirez/linenoise
*
* Does a number of crazy assumptions that happen to be true in 99.9999 % of
* the 2010 UNIX computers around .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Copyright ( c ) 2010 - 2014 , Salvatore Sanfilippo < antirez at gmail dot com >
* Copyright ( c ) 2010 - 2013 , Pieter Noordhuis < pcnoordhuis at gmail dot com >
*
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* 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 in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
* LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* References :
* - http : //invisible-island.net/xterm/ctlseqs/ctlseqs.html
* - http : //www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
*
* Todo list :
* - Filter bogus Ctrl + < char > combinations .
* - Win32 support
*
* Bloat :
* - History search like Ctrl + r in readline ?
*
* List of escape sequences used by this program , we do everything just
* with three sequences . In order to be so cheap we may have some
* flickering effect with some slow terminal , but the lesser sequences
* the more compatible .
*
* EL ( Erase Line )
* Sequence : ESC [ n K
* Effect : if n is 0 or missing , clear from cursor to end of line
* Effect : if n is 1 , clear from beginning of line to cursor
* Effect : if n is 2 , clear entire line
*
* CUF ( CUrsor Forward )
* Sequence : ESC [ n C
* 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
2025-02-05 08:08:41 +01:00
* Effect : reports the current cursor position as ESC [ n ; m R
2025-02-05 08:00:08 +01:00
* 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 .
*
* 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 .
*
* When linenoiseClearScreen ( ) is called , two additional escape sequences
* are used in order to clear the screen and position the cursor at home
* position .
*
* CUP ( Cursor position )
* Sequence : ESC [ H
* Effect : moves the cursor to upper left corner
*
* ED ( Erase display )
* Sequence : ESC [ 2 J
* Effect : clear the whole screen
*
*/
# define _GNU_SOURCE
# define _POSIX_C_SOURCE 200809L /* strdup */
# include "linenoise.h"
# include <ctype.h>
# include <dirent.h>
# include <errno.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <strings.h>
# include <sys/ioctl.h>
# include <sys/stat.h>
# include <termios.h>
# include <unistd.h>
2025-02-05 08:08:41 +01:00
# include "compat.h"
2025-02-05 08:00:08 +01:00
# define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
# define LINENOISE_MAX_LINE 4096
static char * unsupported_term [ ] = { " dumb " , " cons25 " , " emacs " , NULL } ;
static linenoiseCompletionCallback * completionCallback = NULL ;
2025-02-05 08:08:41 +01:00
static linenoiseHintsCallback * hintsCallback = NULL ;
static linenoiseFreeHintsCallback * freeHintsCallback = NULL ;
static char * linenoiseNoTTY ( void ) ;
static void refreshLineWithCompletion ( struct linenoiseState * ls , linenoiseCompletions * lc , int flags ) ;
static void refreshLineWithFlags ( struct linenoiseState * l , int flags ) ;
2025-02-05 08:00:08 +01:00
static struct termios orig_termios ; /* In order to restore at exit.*/
2025-02-05 08:08:41 +01:00
static int maskmode = 0 ; /* Show "***" instead of input. For passwords. */
static int rawmode = 0 ; /* For atexit() function to check if restore is needed*/
2025-02-05 08:00:08 +01:00
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_len = 0 ;
static char * * history = NULL ;
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 ) ;
int linenoiseHistoryAdd ( const char * line ) ;
2025-02-05 08:08:41 +01:00
# define REFRESH_CLEAN (1<<0) // Clean the old prompt from the screen
# define REFRESH_WRITE (1<<1) // Rewrite the prompt on the screen.
# define REFRESH_ALL (REFRESH_CLEAN|REFRESH_WRITE) // Do both.
static void refreshLine ( struct linenoiseState * l ) ;
2025-02-05 08:00:08 +01:00
/* 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 , \
2025-02-05 08:08:41 +01:00
( int ) l - > oldrows , old_rows ) ; \
2025-02-05 08:00:08 +01:00
} \
fprintf ( lndebug_fp , " , " __VA_ARGS__ ) ; \
fflush ( lndebug_fp ) ; \
} while ( 0 )
# else
# define lndebug(...)
# endif
2025-02-05 08:08:41 +01:00
/* ========================== Encoding functions ============================= */
/* Get byte length and column length of the previous character */
static size_t defaultPrevCharLen ( const char * UNUSED ( buf ) , size_t UNUSED ( buf_len ) , size_t UNUSED ( pos ) , size_t * col_len ) {
if ( col_len ! = NULL ) * col_len = 1 ;
return 1 ;
}
/* Get byte length and column length of the next character */
static size_t defaultNextCharLen ( const char * UNUSED ( buf ) , size_t UNUSED ( buf_len ) , size_t UNUSED ( pos ) , size_t * col_len ) {
if ( col_len ! = NULL ) * col_len = 1 ;
return 1 ;
}
/* Read bytes of the next character */
static size_t defaultReadCode ( int fd , char * buf , size_t buf_len , int * c ) {
if ( buf_len < 1 ) return - 1 ;
int nread = read ( fd , & buf [ 0 ] , 1 ) ;
if ( nread = = 1 ) * c = buf [ 0 ] ;
return nread ;
}
/* Set default encoding functions */
static linenoisePrevCharLen * prevCharLen = defaultPrevCharLen ;
static linenoiseNextCharLen * nextCharLen = defaultNextCharLen ;
static linenoiseReadCode * readCode = defaultReadCode ;
/* Set used defined encoding functions */
void linenoiseSetEncodingFunctions (
linenoisePrevCharLen * prevCharLenFunc ,
linenoiseNextCharLen * nextCharLenFunc ,
linenoiseReadCode * readCodeFunc ) {
prevCharLen = prevCharLenFunc ;
nextCharLen = nextCharLenFunc ;
readCode = readCodeFunc ;
}
/* Get column length from begining of buffer to current byte position */
static size_t columnPos ( const char * buf , size_t buf_len , size_t pos ) {
size_t ret = 0 ;
size_t off = 0 ;
while ( off < pos ) {
size_t col_len ;
size_t len = nextCharLen ( buf , buf_len , off , & col_len ) ;
off + = len ;
ret + = col_len ;
}
return ret ;
}
/* Get column length from begining of buffer to current byte position for multiline mode*/
static size_t columnPosForMultiLine ( const char * buf , size_t buf_len , size_t pos , size_t cols , size_t ini_pos ) {
size_t ret = 0 ;
size_t colwid = ini_pos ;
size_t len ;
size_t off = 0 ;
while ( off < buf_len ) {
size_t col_len ;
len = nextCharLen ( buf , buf_len , off , & col_len ) ;
int dif = ( int ) ( colwid + col_len ) - ( int ) cols ;
if ( dif > 0 ) {
ret + = dif ;
colwid = col_len ;
} else if ( dif = = 0 ) {
colwid = 0 ;
} else {
colwid + = col_len ;
}
if ( off > = pos ) break ;
off + = len ;
ret + = col_len ;
}
return ret ;
}
2025-02-05 08:00:08 +01:00
/* ======================= Low level terminal handling ====================== */
2025-02-05 08:08:41 +01:00
/* Enable "mask mode". When it is enabled, instead of the input that
* the user is typing , the terminal will just display a corresponding
* number of asterisks , like " **** " . This is useful for passwords and other
* secrets that should not be displayed . */
void linenoiseMaskModeEnable ( void ) {
maskmode = 1 ;
}
/* Disable mask mode. */
void linenoiseMaskModeDisable ( void ) {
maskmode = 0 ;
}
2025-02-05 08:00:08 +01:00
/* 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 ) {
char * term = getenv ( " TERM " ) ;
int j ;
if ( term = = NULL ) return 0 ;
for ( j = 0 ; unsupported_term [ j ] ; j + + )
if ( ! strcasecmp ( term , unsupported_term [ j ] ) ) return 1 ;
return 0 ;
}
/* Raw mode: 1960 magic shit. */
2025-02-05 08:08:41 +01:00
int enableRawMode ( int fd ) {
2025-02-05 08:00:08 +01:00
struct termios raw ;
if ( ! isatty ( STDIN_FILENO ) ) goto fatal ;
if ( ! atexit_registered ) {
atexit ( linenoiseAtExit ) ;
atexit_registered = 1 ;
}
if ( tcgetattr ( fd , & orig_termios ) = = - 1 ) goto fatal ;
raw = orig_termios ; /* modify the original mode */
/* input modes: no break, no CR to NL, no parity check, no strip char,
* no start / stop output control . */
raw . c_iflag & = ~ ( BRKINT | ICRNL | INPCK | ISTRIP | IXON ) ;
/* output modes - disable post processing */
raw . c_oflag & = ~ ( OPOST ) ;
/* control modes - set 8 bit chars */
raw . c_cflag | = ( CS8 ) ;
/* local modes - choing off, canonical off, no extended functions,
* no signal chars ( ^ Z , ^ C ) */
raw . c_lflag & = ~ ( ECHO | ICANON | IEXTEN | ISIG ) ;
/* control chars - set return condition: min number of bytes and timer.
* We want read to return every single byte , without timeout . */
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 ;
2025-02-05 08:08:41 +01:00
rawmode = 1 ;
2025-02-05 08:00:08 +01:00
return 0 ;
fatal :
errno = ENOTTY ;
return - 1 ;
}
2025-02-05 08:08:41 +01:00
static void disableRawMode ( int fd ) {
2025-02-05 08:00:08 +01:00
/* Don't even check the return value as it's too late. */
2025-02-05 08:08:41 +01:00
if ( rawmode & & tcsetattr ( fd , TCSAFLUSH , & orig_termios ) ! = - 1 )
rawmode = 0 ;
2025-02-05 08:00:08 +01:00
}
/* Use the ESC [6n escape sequence to query the horizontal cursor position
* and return it . On error - 1 is returned , on success the position of the
* cursor . */
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 ;
}
/* 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 ;
if ( ioctl ( 1 , TIOCGWINSZ , & ws ) = = - 1 | | ws . ws_col = = 0 ) {
/* ioctl() failed. Try to query the terminal itself. */
int start , cols ;
/* Get the initial position so we can restore it later. */
start = getCursorPosition ( ifd , ofd ) ;
if ( start = = - 1 ) goto failed ;
/* Go to right margin and get position. */
if ( write ( ofd , " \x1b [999C " , 6 ) ! = 6 ) goto failed ;
cols = getCursorPosition ( ifd , ofd ) ;
if ( cols = = - 1 ) goto failed ;
/* Restore position. */
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 ;
}
failed :
return 80 ;
}
/* 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 " ) ;
fflush ( stderr ) ;
}
/* ============================== Completion ================================ */
2025-02-05 08:08:41 +01:00
static char * get_last_string ( struct linenoiseState * ls ) {
char * hint ;
/* Hint is only the string after the last space */
hint = strrchr ( ls - > buf , ' ' ) ;
if ( ! hint ) {
hint = ls - > buf ;
} else {
+ + hint ;
}
return hint ;
}
2025-02-05 08:00:08 +01:00
/* Free a list of completion option populated by linenoiseAddCompletion(). */
static void freeCompletions ( linenoiseCompletions * lc ) {
size_t i ;
for ( i = 0 ; i < lc - > len ; i + + )
free ( lc - > cvec [ i ] ) ;
if ( lc - > cvec ! = NULL )
free ( lc - > cvec ) ;
}
2025-02-05 08:08:41 +01:00
/* Called by completeLine() and linenoiseShow() to render the current
* edited line with the proposed completion . If the current completion table
* is already available , it is passed as second argument , otherwise the
* function will use the callback to obtain it .
*
* Flags are the same as refreshLine * ( ) , that is REFRESH_ * macros . */
static void refreshLineWithCompletion ( struct linenoiseState * ls , linenoiseCompletions * lc , int flags ) {
/* Obtain the table of completions if the caller didn't provide one. */
linenoiseCompletions ctable = { 0 , 0 , NULL } ;
if ( lc = = NULL ) {
completionCallback ( ls - > buf , get_last_string ( ls ) , & ctable ) ;
lc = & ctable ;
}
/* Show the edited line with completion if possible, or just refresh. */
if ( ls - > completion_idx < lc - > len ) {
struct linenoiseState saved = * ls ;
ls - > len = ls - > pos = strlen ( lc - > cvec [ ls - > completion_idx ] ) ;
ls - > buf = lc - > cvec [ ls - > completion_idx ] ;
refreshLineWithFlags ( ls , flags ) ;
ls - > len = saved . len ;
ls - > pos = saved . pos ;
ls - > buf = saved . buf ;
} else {
refreshLineWithFlags ( ls , flags ) ;
}
/* Free the completions table if needed. */
if ( lc ! = & ctable ) freeCompletions ( & ctable ) ;
}
/* This is an helper function for linenoiseEdit*() and is called when the
2025-02-05 08:00:08 +01:00
* 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
2025-02-05 08:08:41 +01:00
* structure as described in the structure definition .
*
* If the function returns non - zero , the caller should handle the
* returned value as a byte read from the standard input , and process
* it as usually : this basically means that the function may return a byte
* read from the terminal but not processed . Otherwise , if zero is returned ,
* the input was consumed by the completeLine ( ) function to navigate the
* possible completions , and the caller should read for the next characters
* from stdin . */
static char completeLine ( struct linenoiseState * ls , int keypressed ) {
( void ) keypressed ;
2025-02-05 08:00:08 +01:00
linenoiseCompletions lc = { 0 , 0 , NULL } ;
int nread , nwritten , hint_len , hint_line_count , char_count ;
char c = 0 , * common , * hint ;
struct winsize w ;
2025-02-05 08:08:41 +01:00
hint = get_last_string ( ls ) ;
2025-02-05 08:00:08 +01:00
completionCallback ( ls - > buf , hint , & lc ) ;
if ( lc . len = = 0 ) {
linenoiseBeep ( ) ;
2025-02-05 08:08:41 +01:00
ls - > in_completion = 0 ;
2025-02-05 08:00:08 +01:00
} else {
2025-02-05 08:08:41 +01:00
/* This block of code was inserted for the special features of yanglint */
2025-02-05 08:00:08 +01:00
unsigned int i , j ;
/* Learn the longest common part */
common = strdup ( lc . cvec [ 0 ] ) ;
for ( i = 1 ; i < lc . len ; + + i ) {
for ( j = 0 ; j < strlen ( lc . cvec [ i ] ) ; + + j ) {
if ( lc . cvec [ i ] [ j ] ! = common [ j ] ) {
break ;
}
}
common [ j ] = ' \0 ' ;
}
/* Path completions have a different hint */
if ( lc . path & & strrchr ( hint , ' / ' ) ) {
hint = strrchr ( hint , ' / ' ) ;
+ + hint ;
}
/* Show completion */
if ( ( lc . len = = 1 ) & & ( common [ strlen ( common ) - 1 ] ! = ' / ' ) ) {
nwritten = snprintf ( hint , ls - > buflen - ( hint - ls - > buf ) , " %s " , common ) ;
} else {
nwritten = snprintf ( hint , ls - > buflen - ( hint - ls - > buf ) , " %s " , common ) ;
}
free ( common ) ;
ls - > len = ls - > pos = ( hint - ls - > buf ) + nwritten ;
2025-02-05 08:08:41 +01:00
refreshLine ( ls ) ;
2025-02-05 08:00:08 +01:00
/* A single hint */
if ( lc . len = = 1 ) {
freeCompletions ( & lc ) ;
return 0 ;
}
/* Read a char */
nread = read ( ls - > ifd , & c , 1 ) ;
if ( nread < = 0 ) {
freeCompletions ( & lc ) ;
return - 1 ;
}
/* Not a tab */
if ( c ! = 9 ) {
freeCompletions ( & lc ) ;
return c ;
}
/* Learn terminal window size */
ioctl ( ls - > ifd , TIOCGWINSZ , & w ) ;
/* Learn the longest hint */
hint_len = strlen ( lc . cvec [ 0 ] ) ;
for ( i = 1 ; i < lc . len ; + + i ) {
if ( strlen ( lc . cvec [ i ] ) > ( unsigned ) hint_len ) {
hint_len = strlen ( lc . cvec [ i ] ) ;
}
}
/* Learn the number of hints that fit a line */
hint_line_count = 0 ;
do {
/* Still fits, always at least one hint */
+ + hint_line_count ;
char_count = 0 ;
if ( hint_line_count ) {
char_count + = hint_line_count * ( hint_len + 2 ) ;
}
char_count + = hint_len ;
/* Too much */
} while ( char_count < = w . ws_col ) ;
while ( c = = 9 ) {
/* Second tab */
2025-02-05 08:08:41 +01:00
disableRawMode ( ls - > ifd ) ;
2025-02-05 08:00:08 +01:00
printf ( " \n " ) ;
for ( i = 0 ; i < lc . len ; + + i ) {
printf ( " %-*s " , hint_len , lc . cvec [ i ] ) ;
/* Line full or last hint */
if ( ( ( i + 1 ) % hint_line_count = = 0 ) | | ( i = = lc . len - 1 ) ) {
printf ( " \n " ) ;
} else {
printf ( " " ) ;
}
}
2025-02-05 08:08:41 +01:00
enableRawMode ( ls - > ifd ) ;
refreshLine ( ls ) ;
2025-02-05 08:00:08 +01:00
/* Read a char */
nread = read ( ls - > ifd , & c , 1 ) ;
if ( nread < = 0 ) {
freeCompletions ( & lc ) ;
return - 1 ;
}
}
}
freeCompletions ( & lc ) ;
return c ; /* Return last read character */
}
/* Register a callback function to be called for tab-completion. */
void linenoiseSetCompletionCallback ( linenoiseCompletionCallback * fn ) {
completionCallback = fn ;
}
2025-02-05 08:08:41 +01:00
/* Register a hits function to be called to show hits to the user at the
* right of the prompt . */
void linenoiseSetHintsCallback ( linenoiseHintsCallback * fn ) {
hintsCallback = fn ;
}
2025-02-05 08:00:08 +01:00
2025-02-05 08:08:41 +01:00
/* Register a function to free the hints returned by the hints callback
* registered with linenoiseSetHintsCallback ( ) . */
void linenoiseSetFreeHintsCallback ( linenoiseFreeHintsCallback * fn ) {
freeHintsCallback = fn ;
2025-02-05 08:00:08 +01:00
}
/* 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 ) ;
}
2025-02-05 08:08:41 +01:00
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
* to the right of the prompt . */
void refreshShowHints ( struct abuf * ab , struct linenoiseState * l , int pcollen ) {
char seq [ 64 ] ;
size_t collen = pcollen + columnPos ( l - > buf , l - > len , l - > len ) ;
if ( hintsCallback & & collen < l - > cols ) {
int color = - 1 , bold = 0 ;
char * hint = hintsCallback ( l - > buf , & color , & bold ) ;
if ( hint ) {
int hintlen = strlen ( hint ) ;
int hintmaxlen = l - > cols - collen ;
if ( hintlen > hintmaxlen ) hintlen = hintmaxlen ;
if ( bold = = 1 & & color = = - 1 ) color = 37 ;
if ( color ! = - 1 | | bold ! = 0 )
snprintf ( seq , 64 , " \033 [%d;%d;49m " , bold , color ) ;
else
seq [ 0 ] = ' \0 ' ;
abAppend ( ab , seq , strlen ( seq ) ) ;
abAppend ( ab , hint , hintlen ) ;
if ( color ! = - 1 | | bold ! = 0 )
abAppend ( ab , " \033 [0m " , 4 ) ;
/* Call the function to free the hint returned. */
if ( freeHintsCallback ) freeHintsCallback ( hint ) ;
}
}
}
/* Check if text is an ANSI escape sequence
*/
static int isAnsiEscape ( const char * buf , size_t buf_len , size_t * len ) {
if ( buf_len > 2 & & ! memcmp ( " \033 [ " , buf , 2 ) ) {
size_t off = 2 ;
while ( off < buf_len ) {
switch ( buf [ off + + ] ) {
case ' A ' : case ' B ' : case ' C ' : case ' D ' : case ' E ' :
case ' F ' : case ' G ' : case ' H ' : case ' J ' : case ' K ' :
case ' S ' : case ' T ' : case ' f ' : case ' m ' :
* len = off ;
return 1 ;
}
}
}
return 0 ;
}
/* Get column length of prompt text
*/
static size_t promptTextColumnLen ( const char * prompt , size_t plen ) {
char buf [ LINENOISE_MAX_LINE ] ;
size_t buf_len = 0 ;
size_t off = 0 ;
while ( off < plen ) {
size_t len ;
if ( isAnsiEscape ( prompt + off , plen - off , & len ) ) {
off + = len ;
continue ;
}
buf [ buf_len + + ] = prompt [ off + + ] ;
}
return columnPos ( buf , buf_len , buf_len ) ;
}
2025-02-05 08:00:08 +01:00
/* Single line low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content ,
2025-02-05 08:08:41 +01:00
* cursor position , and number of columns of the terminal .
*
* Flags is REFRESH_ * macros . The function can just remove the old
* prompt , just write it , or both . */
static void refreshSingleLine ( struct linenoiseState * l , int flags ) {
2025-02-05 08:00:08 +01:00
char seq [ 64 ] ;
2025-02-05 08:08:41 +01:00
size_t pcollen = promptTextColumnLen ( l - > prompt , strlen ( l - > prompt ) ) ;
2025-02-05 08:00:08 +01:00
int fd = l - > ofd ;
char * buf = l - > buf ;
size_t len = l - > len ;
size_t pos = l - > pos ;
struct abuf ab ;
2025-02-05 08:08:41 +01:00
while ( ( pcollen + columnPos ( buf , len , pos ) ) > = l - > cols ) {
int chlen = nextCharLen ( buf , len , 0 , NULL ) ;
buf + = chlen ;
len - = chlen ;
pos - = chlen ;
2025-02-05 08:00:08 +01:00
}
2025-02-05 08:08:41 +01:00
while ( pcollen + columnPos ( buf , len , len ) > l - > cols ) {
len - = prevCharLen ( buf , len , len , NULL ) ;
2025-02-05 08:00:08 +01:00
}
abInit ( & ab ) ;
/* Cursor to left edge */
2025-02-05 08:08:41 +01:00
snprintf ( seq , sizeof ( seq ) , " \r " ) ;
2025-02-05 08:00:08 +01:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
2025-02-05 08:08:41 +01:00
if ( flags & REFRESH_WRITE ) {
/* Write the prompt and the current buffer content */
abAppend ( & ab , l - > prompt , strlen ( l - > prompt ) ) ;
if ( maskmode = = 1 ) {
while ( len - - ) abAppend ( & ab , " * " , 1 ) ;
} else {
abAppend ( & ab , buf , len ) ;
}
/* Show hits if any. */
refreshShowHints ( & ab , l , pcollen ) ;
}
2025-02-05 08:00:08 +01:00
/* Erase to right */
2025-02-05 08:08:41 +01:00
snprintf ( seq , sizeof ( seq ) , " \x1b [0K " ) ;
2025-02-05 08:00:08 +01:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
2025-02-05 08:08:41 +01:00
if ( flags & REFRESH_WRITE ) {
/* Move cursor to original position. */
snprintf ( seq , sizeof ( seq ) , " \r \x1b [%dC " , ( int ) ( columnPos ( buf , len , pos ) + pcollen ) ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
2025-02-05 08:00:08 +01:00
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 ,
2025-02-05 08:08:41 +01:00
* cursor position , and number of columns of the terminal .
*
* Flags is REFRESH_ * macros . The function can just remove the old
* prompt , just write it , or both . */
static void refreshMultiLine ( struct linenoiseState * l , int flags ) {
2025-02-05 08:00:08 +01:00
char seq [ 64 ] ;
2025-02-05 08:08:41 +01:00
size_t pcollen = promptTextColumnLen ( l - > prompt , strlen ( l - > prompt ) ) ;
size_t colpos = columnPosForMultiLine ( l - > buf , l - > len , l - > len , l - > cols , pcollen ) ;
int colpos2 ; /* cursor column position. */
int rows = ( pcollen + colpos + l - > cols - 1 ) / l - > cols ; /* rows used by current buf. */
int rpos = ( pcollen + l - > oldcolpos + l - > cols ) / l - > cols ; /* cursor relative row. */
2025-02-05 08:00:08 +01:00
int rpos2 ; /* rpos after refresh. */
2025-02-05 08:08:41 +01:00
int col ; /* column position, zero-based. */
int old_rows = l - > oldrows ;
2025-02-05 08:00:08 +01:00
int fd = l - > ofd , j ;
struct abuf ab ;
2025-02-05 08:08:41 +01:00
l - > oldrows = rows ;
2025-02-05 08:00:08 +01:00
/* First step: clear all the lines used before. To do so start by
* going to the last row . */
abInit ( & ab ) ;
2025-02-05 08:08:41 +01:00
if ( ( l - > oldcollen < l - > len ) & & // some character was added
( l - > pos = = l - > len ) & & // at the end of the line
( l - > pos < = l - > cols ) & & // no multiline break
( l - > prev_history_index = = l - > history_index ) // no buffer change based on history
) {
/* Ignore flags and just add one character.
* This code was added because redundant ANSI escape codes make tcl - tests incapable .
*/
abAppend ( & ab , l - > buf + l - > oldcollen , strlen ( l - > buf ) - l - > oldcollen ) ;
l - > oldcolpos = colpos ;
l - > oldcollen = l - > len ;
if ( write ( fd , ab . b , ab . len ) = = - 1 ) { } /* Can't recover from write error. */
abFree ( & ab ) ;
l - > prev_history_index = l - > history_index ;
return ;
2025-02-05 08:00:08 +01:00
}
2025-02-05 08:08:41 +01:00
if ( flags & REFRESH_CLEAN ) {
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 , " \r \x1b [0K \x1b [1A " ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
}
2025-02-05 08:00:08 +01:00
2025-02-05 08:08:41 +01:00
if ( flags & REFRESH_ALL ) {
/* Clean the top line. */
lndebug ( " clear " ) ;
snprintf ( seq , 64 , " \r \x1b [0K " ) ;
2025-02-05 08:00:08 +01:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
2025-02-05 08:08:41 +01:00
/* Get column length to cursor position */
colpos2 = columnPosForMultiLine ( l - > buf , l - > len , l - > pos , l - > cols , pcollen ) ;
if ( flags & REFRESH_WRITE ) {
/* Write the prompt and the current buffer content */
abAppend ( & ab , l - > prompt , strlen ( l - > prompt ) ) ;
if ( maskmode = = 1 ) {
unsigned int i ;
for ( i = 0 ; i < l - > len ; i + + ) abAppend ( & ab , " * " , 1 ) ;
} else {
abAppend ( & ab , l - > buf , l - > len ) ;
}
/* Show hits if any. */
refreshShowHints ( & ab , l , pcollen ) ;
/* 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 & &
( colpos2 + pcollen ) % l - > cols = = 0 )
{
lndebug ( " <newline> " ) ;
abAppend ( & ab , " \n " , 1 ) ;
snprintf ( seq , 64 , " \r " ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
rows + + ;
if ( rows > ( int ) l - > oldrows ) l - > oldrows = rows ;
}
2025-02-05 08:00:08 +01:00
2025-02-05 08:08:41 +01:00
/* Move cursor to right position. */
rpos2 = ( pcollen + colpos2 + l - > cols ) / l - > cols ; /* Current cursor relative row */
lndebug ( " rpos2 %d " , rpos2 ) ;
/* Go up till we reach the expected position. */
if ( rows - rpos2 > 0 ) {
lndebug ( " go-up %d " , rows - rpos2 ) ;
snprintf ( seq , 64 , " \x1b [%dA " , rows - rpos2 ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
/* Set column. */
col = ( pcollen + colpos2 ) % l - > cols ;
lndebug ( " set col %d " , 1 + col ) ;
if ( col )
snprintf ( seq , 64 , " \r \x1b [%dC " , col ) ;
else
snprintf ( seq , 64 , " \r " ) ;
2025-02-05 08:00:08 +01:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
lndebug ( " \n " ) ;
2025-02-05 08:08:41 +01:00
l - > oldcolpos = colpos2 ;
l - > oldcollen = l - > len ;
l - > prev_history_index = l - > history_index ;
2025-02-05 08:00:08 +01:00
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 . */
2025-02-05 08:08:41 +01:00
static void refreshLineWithFlags ( struct linenoiseState * l , int flags ) {
if ( mlmode )
refreshMultiLine ( l , flags ) ;
else
refreshSingleLine ( l , flags ) ;
}
/* Utility function to avoid specifying REFRESH_ALL all the times. */
static void refreshLine ( struct linenoiseState * l ) {
2025-02-05 08:00:08 +01:00
/* Update columns in case the terminal was resized */
2025-02-05 08:08:41 +01:00
l - > cols = getColumns ( STDIN_FILENO , STDOUT_FILENO ) ;
2025-02-05 08:00:08 +01:00
2025-02-05 08:08:41 +01:00
refreshLineWithFlags ( l , REFRESH_ALL ) ;
}
/* Hide the current line, when using the multiplexing API. */
void linenoiseHide ( struct linenoiseState * l ) {
2025-02-05 08:00:08 +01:00
if ( mlmode )
2025-02-05 08:08:41 +01:00
refreshMultiLine ( l , REFRESH_CLEAN ) ;
2025-02-05 08:00:08 +01:00
else
2025-02-05 08:08:41 +01:00
refreshSingleLine ( l , REFRESH_CLEAN ) ;
}
/* Show the current line, when using the multiplexing API. */
void linenoiseShow ( struct linenoiseState * l ) {
if ( l - > in_completion ) {
refreshLineWithCompletion ( l , NULL , REFRESH_WRITE ) ;
} else {
refreshLineWithFlags ( l , REFRESH_WRITE ) ;
}
2025-02-05 08:00:08 +01:00
}
/* Insert the character 'c' at cursor current position.
*
* On error writing to the terminal - 1 is returned , otherwise 0. */
2025-02-05 08:08:41 +01:00
int linenoiseEditInsert ( struct linenoiseState * l , const char * cbuf , int clen ) {
if ( l - > len + clen < = l - > buflen ) {
2025-02-05 08:00:08 +01:00
if ( l - > len = = l - > pos ) {
2025-02-05 08:08:41 +01:00
memcpy ( & l - > buf [ l - > pos ] , cbuf , clen ) ;
l - > pos + = clen ;
l - > len + = clen ; ;
2025-02-05 08:00:08 +01:00
l - > buf [ l - > len ] = ' \0 ' ;
2025-02-05 08:08:41 +01:00
if ( ( ! mlmode & & promptTextColumnLen ( l - > prompt , l - > plen ) + columnPos ( l - > buf , l - > len , l - > len ) < l - > cols & & ! hintsCallback ) ) {
2025-02-05 08:00:08 +01:00
/* Avoid a full update of the line in the
* trivial case . */
2025-02-05 08:08:41 +01:00
if ( maskmode = = 1 ) {
static const char d = ' * ' ;
if ( write ( l - > ofd , & d , 1 ) = = - 1 ) return - 1 ;
} else {
if ( write ( l - > ofd , cbuf , clen ) = = - 1 ) return - 1 ;
}
2025-02-05 08:00:08 +01:00
} else {
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
} else {
2025-02-05 08:08:41 +01:00
memmove ( l - > buf + l - > pos + clen , l - > buf + l - > pos , l - > len - l - > pos ) ;
memcpy ( & l - > buf [ l - > pos ] , cbuf , clen ) ;
l - > pos + = clen ;
l - > len + = clen ;
2025-02-05 08:00:08 +01:00
l - > buf [ l - > len ] = ' \0 ' ;
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
return 0 ;
}
/* Move cursor on the left. */
void linenoiseEditMoveLeft ( struct linenoiseState * l ) {
if ( l - > pos > 0 ) {
2025-02-05 08:08:41 +01:00
l - > pos - = prevCharLen ( l - > buf , l - > len , l - > pos , NULL ) ;
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
/* Move cursor on the right. */
void linenoiseEditMoveRight ( struct linenoiseState * l ) {
if ( l - > pos ! = l - > len ) {
2025-02-05 08:08:41 +01:00
l - > pos + = nextCharLen ( l - > buf , l - > len , l - > pos , NULL ) ;
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
/* Move cursor to the start of the line. */
void linenoiseEditMoveHome ( struct linenoiseState * l ) {
if ( l - > pos ! = 0 ) {
l - > pos = 0 ;
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
/* Move cursor to the end of the line. */
void linenoiseEditMoveEnd ( struct linenoiseState * l ) {
if ( l - > pos ! = l - > len ) {
l - > pos = l - > len ;
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
/* 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 ) ;
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
/* 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 ) {
2025-02-05 08:08:41 +01:00
int chlen = nextCharLen ( l - > buf , l - > len , l - > pos , NULL ) ;
memmove ( l - > buf + l - > pos , l - > buf + l - > pos + chlen , l - > len - l - > pos - chlen ) ;
l - > len - = chlen ;
2025-02-05 08:00:08 +01:00
l - > buf [ l - > len ] = ' \0 ' ;
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
/* Backspace implementation. */
void linenoiseEditBackspace ( struct linenoiseState * l ) {
if ( l - > pos > 0 & & l - > len > 0 ) {
2025-02-05 08:08:41 +01:00
int chlen = prevCharLen ( l - > buf , l - > len , l - > pos , NULL ) ;
memmove ( l - > buf + l - > pos - chlen , l - > buf + l - > pos , l - > len - l - > pos ) ;
l - > pos - = chlen ;
l - > len - = chlen ;
2025-02-05 08:00:08 +01:00
l - > buf [ l - > len ] = ' \0 ' ;
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
}
2025-02-05 08:08:41 +01:00
/* Delete the previous word, maintaining the cursor at the start of the
2025-02-05 08:00:08 +01:00
* current word . */
void linenoiseEditDeletePrevWord ( struct linenoiseState * l ) {
size_t old_pos = l - > pos ;
size_t diff ;
while ( l - > pos > 0 & & l - > buf [ l - > pos - 1 ] = = ' ' )
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 ;
2025-02-05 08:08:41 +01:00
refreshLine ( l ) ;
2025-02-05 08:00:08 +01:00
}
2025-02-05 08:08:41 +01:00
/* This function is part of the multiplexed API of Linenoise, that is used
* in order to implement the blocking variant of the API but can also be
* called by the user directly in an event driven program . It will :
2025-02-05 08:00:08 +01:00
*
2025-02-05 08:08:41 +01:00
* 1. Initialize the linenoise state passed by the user .
* 2. Put the terminal in RAW mode .
* 3. Show the prompt .
* 4. Return control to the user , that will have to call linenoiseEditFeed ( )
* each time there is some data arriving in the standard input .
2025-02-05 08:00:08 +01:00
*
2025-02-05 08:08:41 +01:00
* The user can also call linenoiseEditHide ( ) and linenoiseEditShow ( ) if it
* is required to show some input arriving asynchronously , without mixing
* it with the currently edited line .
*
* When linenoiseEditFeed ( ) returns non - NULL , the user finished with the
* line editing session ( pressed enter CTRL - D / C ) : in this case the caller
* needs to call linenoiseEditStop ( ) to put back the terminal in normal
* mode . This will not destroy the buffer , as long as the linenoiseState
* is still valid in the context of the caller .
*
* The function returns 0 on success , or - 1 if writing to standard output
* fails . If stdin_fd or stdout_fd are set to - 1 , the default is to use
* STDIN_FILENO and STDOUT_FILENO .
*/
int linenoiseEditStart ( struct linenoiseState * l , int stdin_fd , int stdout_fd , char * buf , size_t buflen , const char * prompt ) {
if ( stdin_fd = = - 1 ) stdin_fd = STDIN_FILENO ;
if ( stdout_fd = = - 1 ) stdout_fd = STDOUT_FILENO ;
2025-02-05 08:00:08 +01:00
/* Populate the linenoise state that we pass to functions implementing
* specific editing functionalities . */
2025-02-05 08:08:41 +01:00
l - > in_completion = 0 ;
l - > ifd = stdin_fd ;
l - > ofd = stdout_fd ;
l - > buf = buf ;
l - > buflen = buflen ;
l - > prompt = prompt ;
l - > plen = strlen ( prompt ) ;
l - > oldcolpos = l - > pos = 0 ;
l - > oldcollen = 0 ;
l - > len = 0 ;
/* Enter raw mode. */
if ( enableRawMode ( l - > ifd ) = = - 1 ) return - 1 ;
l - > cols = getColumns ( stdin_fd , stdout_fd ) ;
l - > oldrows = 0 ;
l - > history_index = 0 ;
l - > prev_history_index = 0 ;
2025-02-05 08:00:08 +01:00
/* Buffer starts empty. */
2025-02-05 08:08:41 +01:00
l - > buf [ 0 ] = ' \0 ' ;
l - > buflen - - ; /* Make sure there is always space for the nulterm */
/* If stdin is not a tty, stop here with the initialization. We
* will actually just read a line from standard input in blocking
* mode later , in linenoiseEditFeed ( ) . */
if ( ! isatty ( l - > ifd ) ) return 0 ;
2025-02-05 08:00:08 +01:00
/* The latest history entry is always our current buffer, that
* initially is just an empty string . */
linenoiseHistoryAdd ( " " ) ;
2025-02-05 08:08:41 +01:00
if ( write ( l - > ofd , prompt , l - > plen ) = = - 1 ) return - 1 ;
return 0 ;
}
char * linenoiseEditMore = " If you see this, you are misusing the API: when linenoiseEditFeed() is called, if it returns linenoiseEditMore the user is yet editing the line. See the README file for more information. " ;
/* This function is part of the multiplexed API of linenoise, see the top
* comment on linenoiseEditStart ( ) for more information . Call this function
* each time there is some data to read from the standard input file
* descriptor . In the case of blocking operations , this function can just be
* called in a loop , and block .
*
* The function returns linenoiseEditMore to signal that line editing is still
* in progress , that is , the user didn ' t yet pressed enter / CTRL - D . Otherwise
* the function returns the pointer to the heap - allocated buffer with the
* edited line , that the user should free with linenoiseFree ( ) .
*
* On special conditions , NULL is returned and errno is populated :
*
* EAGAIN if the user pressed Ctrl - C
* ENOENT if the user pressed Ctrl - D
*
* Some other errno : I / O error .
*/
char * linenoiseEditFeed ( struct linenoiseState * l ) {
/* Not a TTY, pass control to line reading without character
* count limits . */
if ( ! isatty ( l - > ifd ) ) return linenoiseNoTTY ( ) ;
int c ;
int nread ;
char cbuf [ 32 ] ; // large enough for any encoding?
char seq [ 3 ] ;
nread = readCode ( l - > ifd , cbuf , sizeof ( cbuf ) , & c ) ;
if ( nread < = 0 ) return NULL ;
/* Only autocomplete when the callback is set. It returns < 0 when
* there was an error reading from fd . Otherwise it will return the
* character that should be handled next . */
if ( ( l - > in_completion | | c = = 9 ) & & completionCallback ! = NULL ) {
c = completeLine ( l , c ) ;
/* BUGFIX of utf-8 support */
* cbuf = c ;
/* Return on errors */
if ( c < 0 ) return NULL ;
/* Read next character when 0 */
if ( c = = 0 ) return linenoiseEditMore ;
}
2025-02-05 08:00:08 +01:00
2025-02-05 08:08:41 +01:00
switch ( c ) {
case ENTER : /* enter */
history_len - - ;
free ( history [ history_len ] ) ;
if ( mlmode ) linenoiseEditMoveEnd ( l ) ;
if ( hintsCallback ) {
/* Force a refresh without hints to leave the previous
* line as the user typed it after a newline . */
linenoiseHintsCallback * hc = hintsCallback ;
hintsCallback = NULL ;
refreshLine ( l ) ;
hintsCallback = hc ;
}
return strdup ( l - > buf ) ;
case CTRL_C : /* ctrl-c */
errno = EAGAIN ;
return NULL ;
case BACKSPACE : /* backspace */
case 8 : /* ctrl-h */
linenoiseEditBackspace ( l ) ;
break ;
case CTRL_D : /* ctrl-d, remove char at right of cursor, or if the
line is empty , act as end - of - file . */
if ( l - > len > 0 ) {
linenoiseEditDelete ( l ) ;
} else {
2025-02-05 08:00:08 +01:00
history_len - - ;
free ( history [ history_len ] ) ;
2025-02-05 08:08:41 +01:00
errno = ENOENT ;
return NULL ;
}
break ;
case CTRL_T : /* ctrl-t, swaps current character with previous. */
if ( l - > pos > 0 & & l - > pos < l - > len ) {
int aux = l - > buf [ l - > pos - 1 ] ;
l - > buf [ l - > pos - 1 ] = l - > buf [ l - > pos ] ;
l - > buf [ l - > pos ] = aux ;
if ( l - > pos ! = l - > len - 1 ) l - > pos + + ;
refreshLine ( l ) ;
}
break ;
case CTRL_B : /* ctrl-b */
linenoiseEditMoveLeft ( l ) ;
break ;
case CTRL_F : /* ctrl-f */
linenoiseEditMoveRight ( l ) ;
break ;
case CTRL_P : /* ctrl-p */
linenoiseEditHistoryNext ( l , LINENOISE_HISTORY_PREV ) ;
break ;
case CTRL_N : /* ctrl-n */
linenoiseEditHistoryNext ( l , LINENOISE_HISTORY_NEXT ) ;
break ;
case ESC : /* escape sequence */
/* Read the next two bytes representing the escape sequence.
* Use two calls to handle slow terminals returning the two
* chars at different times . */
if ( read ( l - > ifd , seq , 1 ) = = - 1 ) break ;
if ( read ( l - > ifd , seq + 1 , 1 ) = = - 1 ) break ;
/* ESC [ sequences. */
if ( seq [ 0 ] = = ' [ ' ) {
if ( seq [ 1 ] > = ' 0 ' & & seq [ 1 ] < = ' 9 ' ) {
/* Extended escape, read additional byte. */
if ( read ( l - > ifd , seq + 2 , 1 ) = = - 1 ) break ;
if ( seq [ 2 ] = = ' ~ ' ) {
2025-02-05 08:00:08 +01:00
switch ( seq [ 1 ] ) {
2025-02-05 08:08:41 +01:00
case ' 3 ' : /* Delete key. */
linenoiseEditDelete ( l ) ;
2025-02-05 08:00:08 +01:00
break ;
}
}
2025-02-05 08:08:41 +01:00
} else {
2025-02-05 08:00:08 +01:00
switch ( seq [ 1 ] ) {
2025-02-05 08:08:41 +01:00
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 ;
2025-02-05 08:00:08 +01:00
case ' H ' : /* Home */
2025-02-05 08:08:41 +01:00
linenoiseEditMoveHome ( l ) ;
2025-02-05 08:00:08 +01:00
break ;
case ' F ' : /* End*/
2025-02-05 08:08:41 +01:00
linenoiseEditMoveEnd ( l ) ;
2025-02-05 08:00:08 +01:00
break ;
}
}
}
2025-02-05 08:08:41 +01:00
/* ESC O sequences. */
else if ( seq [ 0 ] = = ' O ' ) {
switch ( seq [ 1 ] ) {
case ' H ' : /* Home */
linenoiseEditMoveHome ( l ) ;
break ;
case ' F ' : /* End*/
linenoiseEditMoveEnd ( l ) ;
break ;
}
}
break ;
default :
if ( linenoiseEditInsert ( l , cbuf , nread ) ) return NULL ;
break ;
case CTRL_U : /* Ctrl+u, delete the whole line. */
l - > buf [ 0 ] = ' \0 ' ;
l - > pos = l - > len = 0 ;
refreshLine ( l ) ;
break ;
case CTRL_K : /* Ctrl+k, delete from current to end of line. */
l - > buf [ l - > pos ] = ' \0 ' ;
l - > len = l - > pos ;
refreshLine ( l ) ;
break ;
case CTRL_A : /* Ctrl+a, go to the start of the line */
linenoiseEditMoveHome ( l ) ;
break ;
case CTRL_E : /* ctrl+e, go to the end of the line */
linenoiseEditMoveEnd ( l ) ;
break ;
case CTRL_L : /* ctrl+l, clear screen */
linenoiseClearScreen ( ) ;
refreshLine ( l ) ;
break ;
case CTRL_W : /* ctrl+w, delete previous word */
linenoiseEditDeletePrevWord ( l ) ;
break ;
2025-02-05 08:00:08 +01:00
}
2025-02-05 08:08:41 +01:00
return linenoiseEditMore ;
}
/* This is part of the multiplexed linenoise API. See linenoiseEditStart()
* for more information . This function is called when linenoiseEditFeed ( )
* returns something different than NULL . At this point the user input
* is in the buffer , and we can restore the terminal in normal mode . */
void linenoiseEditStop ( struct linenoiseState * l ) {
if ( ! isatty ( l - > ifd ) ) return ;
disableRawMode ( l - > ifd ) ;
printf ( " \n " ) ;
}
/* This just implements a blocking loop for the multiplexed API.
* In many applications that are not event - driven , we can just call
* the blocking linenoise API , wait for the user to complete the editing
* and return the buffer . */
static char * linenoiseBlockingEdit ( int stdin_fd , int stdout_fd , char * buf , size_t buflen , const char * prompt )
{
struct linenoiseState l ;
/* Editing without a buffer is invalid. */
if ( buflen = = 0 ) {
errno = EINVAL ;
return NULL ;
}
linenoiseEditStart ( & l , stdin_fd , stdout_fd , buf , buflen , prompt ) ;
char * res ;
while ( ( res = linenoiseEditFeed ( & l ) ) = = linenoiseEditMore ) ;
linenoiseEditStop ( & l ) ;
return res ;
2025-02-05 08:00:08 +01:00
}
/* 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 " ) ;
2025-02-05 08:08:41 +01:00
if ( enableRawMode ( STDIN_FILENO ) = = - 1 ) return ;
2025-02-05 08:00:08 +01:00
memset ( quit , ' ' , 4 ) ;
while ( 1 ) {
2025-02-05 08:08:41 +01:00
char c = 0 ;
2025-02-05 08:00:08 +01:00
int nread ;
2025-02-05 08:08:41 +01:00
nread = read ( STDIN_FILENO , & c , sizeof c ) ;
2025-02-05 08:00:08 +01:00
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 " ,
2025-02-05 08:08:41 +01:00
isprint ( ( int ) c ) ? c : ' ? ' , ( int ) c , ( int ) c ) ;
2025-02-05 08:00:08 +01:00
printf ( " \r " ) ; /* Go left edge manually, we are in raw mode. */
fflush ( stdout ) ;
}
2025-02-05 08:08:41 +01:00
disableRawMode ( STDIN_FILENO ) ;
2025-02-05 08:00:08 +01:00
}
2025-02-05 08:08:41 +01:00
/* This function is called when linenoise() is called with the standard
* input file descriptor not attached to a TTY . So for example when the
* program using linenoise is called in pipe or with a file redirected
* to its standard input . In this case , we want to be able to return the
* line regardless of its length ( by default we are limited to 4 k ) . */
static char * linenoiseNoTTY ( void ) {
char * line = NULL ;
size_t len = 0 , maxlen = 0 ;
2025-02-05 08:00:08 +01:00
2025-02-05 08:08:41 +01:00
while ( 1 ) {
if ( len = = maxlen ) {
if ( maxlen = = 0 ) maxlen = 16 ;
maxlen * = 2 ;
char * oldval = line ;
line = realloc ( line , maxlen ) ;
if ( line = = NULL ) {
if ( oldval ) free ( oldval ) ;
return NULL ;
}
}
int c = fgetc ( stdin ) ;
if ( c = = EOF | | c = = ' \n ' ) {
if ( c = = EOF & & len = = 0 ) {
free ( line ) ;
return NULL ;
} else {
line [ len ] = ' \0 ' ;
return line ;
}
} else {
line [ len ] = c ;
len + + ;
2025-02-05 08:00:08 +01:00
}
}
}
/* 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 buf [ LINENOISE_MAX_LINE ] ;
2025-02-05 08:08:41 +01:00
if ( ! isatty ( STDIN_FILENO ) ) {
/* Not a tty: read from file / pipe. In this mode we don't want any
* limit to the line size , so we call a function to handle that . */
return linenoiseNoTTY ( ) ;
} else if ( isUnsupportedTerm ( ) ) {
2025-02-05 08:00:08 +01:00
size_t len ;
printf ( " %s " , prompt ) ;
fflush ( stdout ) ;
if ( fgets ( buf , LINENOISE_MAX_LINE , stdin ) = = NULL ) return NULL ;
len = strlen ( buf ) ;
while ( len & & ( buf [ len - 1 ] = = ' \n ' | | buf [ len - 1 ] = = ' \r ' ) ) {
len - - ;
buf [ len ] = ' \0 ' ;
}
return strdup ( buf ) ;
} else {
2025-02-05 08:08:41 +01:00
char * retval = linenoiseBlockingEdit ( STDIN_FILENO , STDOUT_FILENO , buf , LINENOISE_MAX_LINE , prompt ) ;
return retval ;
2025-02-05 08:00:08 +01:00
}
}
2025-02-05 08:08:41 +01:00
/* This is just a wrapper the user may want to call in order to make sure
* the linenoise returned buffer is freed with the same allocator it was
* created with . Useful when the main program is using an alternative
* allocator . */
void linenoiseFree ( void * ptr ) {
if ( ptr = = linenoiseEditMore ) return ; // Protect from API misuse.
free ( ptr ) ;
}
2025-02-05 08:00:08 +01:00
/* ================================ History ================================= */
/* 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 ) ;
}
}
/* At exit we'll try to fix the terminal to the initial conditions. */
static void linenoiseAtExit ( void ) {
2025-02-05 08:08:41 +01:00
disableRawMode ( STDIN_FILENO ) ;
2025-02-05 08:00:08 +01:00
freeHistory ( ) ;
}
/* 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 ) {
char * linecopy ;
if ( history_max_len = = 0 ) return 0 ;
/* Initialization on first call. */
if ( history = = NULL ) {
history = malloc ( sizeof ( char * ) * history_max_len ) ;
if ( history = = NULL ) return 0 ;
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 ) ;
if ( ! linecopy ) return 0 ;
if ( history_len = = history_max_len ) {
free ( history [ 0 ] ) ;
memmove ( history , history + 1 , sizeof ( char * ) * ( history_max_len - 1 ) ) ;
history_len - - ;
}
history [ history_len ] = linecopy ;
history_len + + ;
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 ) {
char * * new ;
if ( len < 1 ) return 0 ;
if ( history ) {
int tocopy = history_len ;
new = malloc ( sizeof ( char * ) * len ) ;
if ( new = = NULL ) return 0 ;
/* 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 ) ;
history = new ;
}
history_max_len = len ;
if ( history_len > history_max_len )
history_len = history_max_len ;
return 1 ;
}
/* Save the history in the specified file. On success 0 is returned
* otherwise - 1 is returned . */
int linenoiseHistorySave ( const char * filename ) {
2025-02-05 08:08:41 +01:00
mode_t old_umask = umask ( S_IXUSR | S_IRWXG | S_IRWXO ) ;
FILE * fp ;
2025-02-05 08:00:08 +01:00
int j ;
2025-02-05 08:08:41 +01:00
fp = fopen ( filename , " w " ) ;
umask ( old_umask ) ;
2025-02-05 08:00:08 +01:00
if ( fp = = NULL ) return - 1 ;
2025-02-05 08:08:41 +01:00
chmod ( filename , S_IRUSR | S_IWUSR ) ;
2025-02-05 08:00:08 +01:00
for ( j = 0 ; j < history_len ; j + + )
fprintf ( fp , " %s \n " , history [ j ] ) ;
fclose ( fp ) ;
return 0 ;
}
/* Load the history from the specified file. If the file does not exist
* zero is returned and no operation is performed .
*
* If the file exists and the operation succeeded 0 is returned , otherwise
* on error - 1 is returned . */
int linenoiseHistoryLoad ( const char * filename ) {
FILE * fp = fopen ( filename , " r " ) ;
char buf [ LINENOISE_MAX_LINE ] ;
if ( fp = = NULL ) return - 1 ;
while ( fgets ( buf , LINENOISE_MAX_LINE , fp ) ! = NULL ) {
char * p ;
p = strchr ( buf , ' \r ' ) ;
if ( ! p ) p = strchr ( buf , ' \n ' ) ;
if ( p ) * p = ' \0 ' ;
linenoiseHistoryAdd ( buf ) ;
}
fclose ( fp ) ;
return 0 ;
}