2025-01-18 15:42:31 +01:00
# ifndef _WIN32
/*
* You can find the latest source code at :
*
* http : //github.com/ericcurtin/linenoise.cpp
*
* Does a number of crazy assumptions that happen to be true in 99.9999 % of
* the 2010 UNIX computers around .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Copyright ( c ) 2010 - 2023 , Salvatore Sanfilippo < antirez at gmail dot com >
* Copyright ( c ) 2010 - 2013 , Pieter Noordhuis < pcnoordhuis at gmail dot com >
* Copyright ( c ) 2025 , Eric Curtin < ericcurtin17 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
* Effect : reports the current cusor position as ESC [ n ; m R
* where n is the row and m is the column
*
* When multi line mode is enabled , we also use an additional escape
* sequence . However multi line editing is disabled by default .
*
* 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
*
*/
2025-01-21 10:32:35 +01:00
# include "linenoise.h"
# include <ctype.h>
# include <errno.h>
# include <stdio.h>
# include <string.h>
# include <sys / file.h>
# include <sys / ioctl.h>
# include <sys / stat.h>
# include <sys / types.h>
# include <termios.h>
# include <unistd.h>
# include <memory>
# include <string>
# include <vector>
# define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
# define LINENOISE_MAX_LINE 4096
static std : : vector < const char * > unsupported_term = { " dumb " , " cons25 " , " emacs " } ;
2025-01-18 15:42:31 +01:00
static linenoiseCompletionCallback * completionCallback = NULL ;
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 ) ;
static struct termios orig_termios ; /* In order to restore at exit.*/
static int maskmode = 0 ; /* Show "***" instead of input. For passwords. */
static int rawmode = 0 ; /* For atexit() function to check if restore is needed*/
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 ) ;
# 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-01-21 10:32:35 +01:00
class File {
public :
FILE * file = nullptr ;
FILE * open ( const std : : string & filename , const char * mode ) {
file = fopen ( filename . c_str ( ) , mode ) ;
return file ;
}
int lock ( ) {
if ( file ) {
fd = fileno ( file ) ;
if ( flock ( fd , LOCK_EX | LOCK_NB ) ! = 0 ) {
fd = - 1 ;
return 1 ;
}
}
return 0 ;
}
~ File ( ) {
if ( fd > = 0 ) {
flock ( fd , LOCK_UN ) ;
}
if ( file ) {
fclose ( file ) ;
}
}
private :
int fd = - 1 ;
} ;
2025-01-18 15:42:31 +01:00
__attribute__ ( ( format ( printf , 1 , 2 ) ) )
/* Debugging function. */
#if 0
static void lndebug ( const char * fmt , . . . ) {
2025-01-21 10:32:35 +01:00
static File file ;
if ( file . file = = nullptr ) {
file . open ( " /tmp/lndebug.txt " , " a " ) ;
2025-01-18 15:42:31 +01:00
}
2025-01-21 10:32:35 +01:00
if ( file . file ! = nullptr ) {
2025-01-18 15:42:31 +01:00
va_list args ;
va_start ( args , fmt ) ;
2025-01-21 10:32:35 +01:00
vfprintf ( file . file , fmt , args ) ;
2025-01-18 15:42:31 +01:00
va_end ( args ) ;
2025-01-21 10:32:35 +01:00
fflush ( file . file ) ;
2025-01-18 15:42:31 +01:00
}
}
# else
static void lndebug ( const char * , . . . ) {
}
# endif
/* ======================= Low level terminal handling ====================== */
/* 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 ;
}
/* 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 " ) ;
if ( term = = NULL ) return 0 ;
2025-01-21 10:32:35 +01:00
for ( size_t j = 0 ; j < unsupported_term . size ( ) ; + + j ) {
if ( ! strcasecmp ( term , unsupported_term [ j ] ) ) {
return 1 ;
}
}
2025-01-18 15:42:31 +01:00
return 0 ;
}
/* Raw mode: 1960 magic shit. */
static int enableRawMode ( int fd ) {
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 ;
rawmode = 1 ;
return 0 ;
fatal :
errno = ENOTTY ;
return - 1 ;
}
static void disableRawMode ( int fd ) {
/* Don't even check the return value as it's too late. */
if ( rawmode & & tcsetattr ( fd , TCSAFLUSH , & orig_termios ) ! = - 1 )
rawmode = 0 ;
}
/* 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 ) ;
}
/* 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. */
2025-01-21 10:32:35 +01:00
linenoiseCompletions ctable ;
2025-01-18 15:42:31 +01:00
if ( lc = = NULL ) {
2025-01-21 10:32:35 +01:00
completionCallback ( ls - > buf , & ctable ) ;
2025-01-18 15:42:31 +01:00
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 ] ;
2025-01-21 10:32:35 +01:00
refreshLineWithFlags ( ls , flags ) ;
2025-01-18 15:42:31 +01:00
ls - > len = saved . len ;
ls - > pos = saved . pos ;
ls - > buf = saved . buf ;
} else {
2025-01-21 10:32:35 +01:00
refreshLineWithFlags ( ls , flags ) ;
2025-01-18 15:42:31 +01:00
}
2025-01-21 10:32:35 +01:00
if ( lc = = & ctable ) {
ctable . to_free = false ;
}
2025-01-18 15:42:31 +01:00
}
/* 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 .
*
* 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 termianl 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 int completeLine ( struct linenoiseState * ls , int keypressed ) {
2025-01-21 10:32:35 +01:00
linenoiseCompletions lc ;
2025-01-18 15:42:31 +01:00
int nwritten ;
char c = keypressed ;
2025-01-21 10:32:35 +01:00
completionCallback ( ls - > buf , & lc ) ;
2025-01-18 15:42:31 +01:00
if ( lc . len = = 0 ) {
linenoiseBeep ( ) ;
ls - > in_completion = 0 ;
} else {
switch ( c ) {
case 9 : /* tab */
if ( ls - > in_completion = = 0 ) {
ls - > in_completion = 1 ;
ls - > completion_idx = 0 ;
} else {
2025-01-21 10:32:35 +01:00
ls - > completion_idx = ( ls - > completion_idx + 1 ) % ( lc . len + 1 ) ;
2025-01-18 15:42:31 +01:00
if ( ls - > completion_idx = = lc . len ) linenoiseBeep ( ) ;
}
c = 0 ;
break ;
case 27 : /* escape */
/* Re-show original buffer */
if ( ls - > completion_idx < lc . len ) refreshLine ( ls ) ;
ls - > in_completion = 0 ;
c = 0 ;
break ;
default :
/* Update buffer and return */
if ( ls - > completion_idx < lc . len ) {
2025-01-21 10:32:35 +01:00
nwritten = snprintf ( ls - > buf , ls - > buflen , " %s " , lc . cvec [ ls - > completion_idx ] ) ;
2025-01-18 15:42:31 +01:00
ls - > len = ls - > pos = nwritten ;
}
ls - > in_completion = 0 ;
break ;
}
/* Show completion or original buffer */
if ( ls - > in_completion & & ls - > completion_idx < lc . len ) {
2025-01-21 10:32:35 +01:00
refreshLineWithCompletion ( ls , & lc , REFRESH_ALL ) ;
2025-01-18 15:42:31 +01:00
} else {
refreshLine ( ls ) ;
}
}
return c ; /* Return last read character */
}
/* Register a callback function to be called for tab-completion. */
void linenoiseSetCompletionCallback ( linenoiseCompletionCallback * fn ) {
completionCallback = fn ;
}
/* 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 ;
}
/* Register a function to free the hints returned by the hints callback
* registered with linenoiseSetHintsCallback ( ) . */
void linenoiseSetFreeHintsCallback ( linenoiseFreeHintsCallback * fn ) {
freeHintsCallback = 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 ) {
2025-01-21 10:32:35 +01:00
const size_t len = strlen ( str ) ;
auto copy = std : : make_unique < char [ ] > ( len + 1 ) ;
if ( ! copy ) {
2025-01-18 15:42:31 +01:00
return ;
}
2025-01-21 10:32:35 +01:00
memcpy ( copy . get ( ) , str , len + 1 ) ;
char * * cvec = static_cast < char * * > ( std : : realloc ( lc - > cvec , sizeof ( char * ) * ( lc - > len + 1 ) ) ) ;
if ( cvec = = nullptr ) {
return ;
}
2025-01-18 15:42:31 +01:00
2025-01-21 10:32:35 +01:00
lc - > cvec = cvec ;
lc - > cvec [ lc - > len + + ] = copy . release ( ) ;
2025-01-18 15:42:31 +01:00
}
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
* to the right of the prompt . */
2025-01-21 10:32:35 +01:00
static void refreshShowHints ( std : : string & ab , struct linenoiseState * l , int plen ) {
2025-01-18 15:42:31 +01:00
char seq [ 64 ] ;
if ( hintsCallback & & plen + l - > len < l - > cols ) {
int color = - 1 , bold = 0 ;
const char * hint = hintsCallback ( l - > buf , & color , & bold ) ;
if ( hint ) {
int hintlen = strlen ( hint ) ;
int hintmaxlen = l - > cols - ( plen + l - > len ) ;
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 ' ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
ab . append ( hint , hintlen ) ;
2025-01-18 15:42:31 +01:00
if ( color ! = - 1 | | bold ! = 0 )
2025-01-21 10:32:35 +01:00
ab . append ( " \033 [0m " ) ;
2025-01-18 15:42:31 +01:00
/* Call the function to free the hint returned. */
if ( freeHintsCallback ) freeHintsCallback ( hint ) ;
}
}
}
/* 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 .
*
* 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 ) {
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 ;
2025-01-21 10:32:35 +01:00
std : : string ab ;
2025-01-18 15:42:31 +01:00
while ( ( plen + pos ) > = l - > cols ) {
buf + + ;
len - - ;
pos - - ;
}
while ( plen + len > l - > cols ) {
len - - ;
}
/* Cursor to left edge */
snprintf ( seq , sizeof ( seq ) , " \r " ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
if ( flags & REFRESH_WRITE ) {
/* Write the prompt and the current buffer content */
2025-01-21 10:32:35 +01:00
ab . append ( l - > prompt ) ;
2025-01-18 15:42:31 +01:00
if ( maskmode = = 1 ) {
2025-01-21 10:32:35 +01:00
while ( len - - ) {
ab . append ( " * " ) ;
}
2025-01-18 15:42:31 +01:00
} else {
2025-01-21 10:32:35 +01:00
ab . append ( buf , len ) ;
2025-01-18 15:42:31 +01:00
}
/* Show hits if any. */
2025-01-21 10:32:35 +01:00
refreshShowHints ( ab , l , plen ) ;
2025-01-18 15:42:31 +01:00
}
/* Erase to right */
snprintf ( seq , sizeof ( seq ) , " \x1b [0K " ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
if ( flags & REFRESH_WRITE ) {
/* Move cursor to original position. */
snprintf ( seq , sizeof ( seq ) , " \r \x1b [%dC " , ( int ) ( pos + plen ) ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
}
2025-01-21 10:32:35 +01:00
( void ) ! write ( fd , ab . c_str ( ) , ab . size ( ) ) ; /* Can't recover from write error. */
2025-01-18 15:42:31 +01:00
}
/* 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 .
*
* 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 ) {
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 col ; /* colum position, zero-based. */
int old_rows = l - > oldrows ;
int fd = l - > ofd , j ;
2025-01-21 10:32:35 +01:00
std : : string ab ;
2025-01-18 15:42:31 +01:00
l - > oldrows = rows ;
/* First step: clear all the lines used before. To do so start by
* going to the last row . */
if ( flags & REFRESH_CLEAN ) {
if ( old_rows - rpos > 0 ) {
lndebug ( " go down %d " , old_rows - rpos ) ;
snprintf ( seq , 64 , " \x1b [%dB " , old_rows - rpos ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
}
/* 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 " ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
}
}
if ( flags & REFRESH_ALL ) {
/* Clean the top line. */
lndebug ( " clear " ) ;
snprintf ( seq , 64 , " \r \x1b [0K " ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
}
if ( flags & REFRESH_WRITE ) {
/* Write the prompt and the current buffer content */
2025-01-21 10:32:35 +01:00
ab . append ( l - > prompt ) ;
2025-01-18 15:42:31 +01:00
if ( maskmode = = 1 ) {
2025-01-21 10:32:35 +01:00
for ( unsigned int i = 0 ; i < l - > len ; + + i ) {
ab . append ( " * " ) ;
}
2025-01-18 15:42:31 +01:00
} else {
2025-01-21 10:32:35 +01:00
ab . append ( l - > buf , l - > len ) ;
2025-01-18 15:42:31 +01:00
}
/* Show hits if any. */
2025-01-21 10:32:35 +01:00
refreshShowHints ( ab , l , plen ) ;
2025-01-18 15:42:31 +01:00
/* 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> " ) ;
2025-01-21 10:32:35 +01:00
ab . append ( " \n " ) ;
2025-01-18 15:42:31 +01:00
snprintf ( seq , 64 , " \r " ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
rows + + ;
if ( rows > ( int ) l - > oldrows ) l - > oldrows = 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 ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
}
/* Set column. */
col = ( plen + ( int ) l - > pos ) % ( int ) l - > cols ;
lndebug ( " set col %d " , 1 + col ) ;
if ( col )
snprintf ( seq , 64 , " \r \x1b [%dC " , col ) ;
else
snprintf ( seq , 64 , " \r " ) ;
2025-01-21 10:32:35 +01:00
ab . append ( seq ) ;
2025-01-18 15:42:31 +01:00
}
lndebug ( " \n " ) ;
l - > oldpos = l - > pos ;
2025-01-21 10:32:35 +01:00
( void ) ! write ( fd , ab . c_str ( ) , ab . size ( ) ) ; /* Can't recover from write error. */
2025-01-18 15:42:31 +01:00
}
/* Calls the two low level functions refreshSingleLine() or
* refreshMultiLine ( ) according to the selected mode . */
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 ) {
refreshLineWithFlags ( l , REFRESH_ALL ) ;
}
/* Hide the current line, when using the multiplexing API. */
void linenoiseHide ( struct linenoiseState * l ) {
if ( mlmode )
refreshMultiLine ( l , REFRESH_CLEAN ) ;
else
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 ) ;
}
}
/* Insert the character 'c' at cursor current position.
*
* On error writing to the terminal - 1 is returned , otherwise 0. */
static 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 & & ! hintsCallback ) ) {
/* Avoid a full update of the line in the
* trivial case . */
char d = ( maskmode = = 1 ) ? ' * ' : c ;
if ( write ( l - > ofd , & d , 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. */
static void linenoiseEditMoveLeft ( struct linenoiseState * l ) {
if ( l - > pos > 0 ) {
l - > pos - - ;
refreshLine ( l ) ;
}
}
/* Move cursor on the right. */
static void linenoiseEditMoveRight ( struct linenoiseState * l ) {
if ( l - > pos ! = l - > len ) {
l - > pos + + ;
refreshLine ( l ) ;
}
}
/* Move cursor to the start of the line. */
static void linenoiseEditMoveHome ( struct linenoiseState * l ) {
if ( l - > pos ! = 0 ) {
l - > pos = 0 ;
refreshLine ( l ) ;
}
}
/* Move cursor to the end of the line. */
static 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
static 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 . */
static 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. */
static 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 ) ;
}
}
/* Delete the previosu word, maintaining the cursor at the start of the
* current word . */
static 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 ;
refreshLine ( l ) ;
}
/* 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 :
*
* 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 .
*
* The user can also call linenoiseEditHide ( ) and linenoiseEditShow ( ) if it
* is required to show some input arriving asyncronously , 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 ) {
/* Populate the linenoise state that we pass to functions implementing
* specific editing functionalities . */
l - > in_completion = 0 ;
l - > ifd = stdin_fd ! = - 1 ? stdin_fd : STDIN_FILENO ;
l - > ofd = stdout_fd ! = - 1 ? stdout_fd : STDOUT_FILENO ;
l - > buf = buf ;
l - > buflen = buflen ;
l - > prompt = prompt ;
l - > plen = strlen ( prompt ) ;
l - > oldpos = l - > pos = 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 ;
/* Buffer starts empty. */
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 ;
/* The latest history entry is always our current buffer, that
* initially is just an empty string . */
linenoiseHistoryAdd ( " " ) ;
if ( write ( l - > ofd , prompt , l - > plen ) = = - 1 ) return - 1 ;
return 0 ;
}
const 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 .
*/
const char * linenoiseEditFeed ( struct linenoiseState * l ) {
/* Not a TTY, pass control to line reading without character
* count limits . */
if ( ! isatty ( l - > ifd ) ) return linenoiseNoTTY ( ) ;
char c ;
int nread ;
char seq [ 3 ] ;
nread = read ( l - > ifd , & c , 1 ) ;
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 ) ;
/* Read next character when 0 */
if ( c = = 0 ) return linenoiseEditMore ;
}
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 {
history_len - - ;
free ( history [ history_len ] ) ;
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 ] = = ' ~ ' ) {
switch ( seq [ 1 ] ) {
case ' 3 ' : /* Delete key. */
linenoiseEditDelete ( l ) ;
break ;
}
}
} 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 ;
case ' F ' : /* End*/
linenoiseEditMoveEnd ( l ) ;
break ;
}
}
}
/* 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 , c ) ) 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 ;
}
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 - drivern , we can just call
* the blocking linenoise API , wait for the user to complete the editing
* and return the buffer . */
static const 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 ) ;
const char * res ;
while ( ( res = linenoiseEditFeed ( & l ) ) = = linenoiseEditMore ) ;
linenoiseEditStop ( & l ) ;
return res ;
}
/* 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 ( " \r " ) ; /* Go left edge manually, we are in raw mode. */
fflush ( stdout ) ;
}
disableRawMode ( STDIN_FILENO ) ;
}
/* 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 ;
while ( 1 ) {
if ( len = = maxlen ) {
if ( maxlen = = 0 ) maxlen = 16 ;
maxlen * = 2 ;
char * oldval = line ;
line = ( char * ) 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 + + ;
}
}
}
/* 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 . */
const char * linenoise ( const char * prompt ) {
char buf [ LINENOISE_MAX_LINE ] ;
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 ( ) ) {
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 {
const char * retval = linenoiseBlockingEdit ( STDIN_FILENO , STDOUT_FILENO , buf , LINENOISE_MAX_LINE , prompt ) ;
return retval ;
}
}
/* 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 ) ;
}
/* ================================ 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 ) {
disableRawMode ( STDIN_FILENO ) ;
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 = ( char * * ) 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_ptr ;
if ( len < 1 ) return 0 ;
if ( history ) {
int tocopy = history_len ;
new_ptr = ( char * * ) malloc ( sizeof ( char * ) * len ) ;
if ( new_ptr = = 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_ptr , 0 , sizeof ( char * ) * len ) ;
memcpy ( new_ptr , history + ( history_len - tocopy ) , sizeof ( char * ) * tocopy ) ;
free ( history ) ;
history = new_ptr ;
}
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 ) {
mode_t old_umask = umask ( S_IXUSR | S_IRWXG | S_IRWXO ) ;
2025-01-21 10:32:35 +01:00
File file ;
file . open ( filename , " w " ) ;
2025-01-18 15:42:31 +01:00
umask ( old_umask ) ;
2025-01-21 10:32:35 +01:00
if ( file . file = = NULL ) {
return - 1 ;
}
2025-01-18 15:42:31 +01:00
chmod ( filename , S_IRUSR | S_IWUSR ) ;
2025-01-21 10:32:35 +01:00
for ( int j = 0 ; j < history_len ; + + j ) {
fprintf ( file . file , " %s \n " , history [ j ] ) ;
}
2025-01-18 15:42:31 +01:00
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 ) {
2025-01-21 10:32:35 +01:00
File file ;
file . open ( filename , " r " ) ;
2025-01-18 15:42:31 +01:00
char buf [ LINENOISE_MAX_LINE ] ;
2025-01-21 10:32:35 +01:00
if ( file . file = = NULL ) {
return - 1 ;
}
2025-01-18 15:42:31 +01:00
2025-01-21 10:32:35 +01:00
while ( fgets ( buf , LINENOISE_MAX_LINE , file . file ) ! = NULL ) {
2025-01-18 15:42:31 +01:00
char * p ;
p = strchr ( buf , ' \r ' ) ;
if ( ! p ) p = strchr ( buf , ' \n ' ) ;
if ( p ) * p = ' \0 ' ;
linenoiseHistoryAdd ( buf ) ;
}
return 0 ;
}
# endif