2025-01-18 14:42:31 +00: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 09:32:35 +00:00
# include "linenoise.h"
2025-01-18 14:42:31 +00:00
2025-01-21 09:32:35 +00:00
# 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 14:42:31 +00: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 09:32:35 +00: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 14:42:31 +00:00
__attribute__ (( format ( printf , 1 , 2 )))
/* Debugging function. */
#if 0
static void lndebug(const char *fmt, ...) {
2025-01-21 09:32:35 +00:00
static File file;
if (file.file == nullptr) {
file.open("/tmp/lndebug.txt", "a");
2025-01-18 14:42:31 +00:00
}
2025-01-21 09:32:35 +00:00
if (file.file != nullptr) {
2025-01-18 14:42:31 +00:00
va_list args;
va_start(args, fmt);
2025-01-21 09:32:35 +00:00
vfprintf(file.file, fmt, args);
2025-01-18 14:42:31 +00:00
va_end(args);
2025-01-21 09:32:35 +00:00
fflush(file.file);
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
for ( size_t j = 0 ; j < unsupported_term . size (); ++ j ) {
if ( ! strcasecmp ( term , unsupported_term [ j ])) {
return 1 ;
}
}
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
linenoiseCompletions ctable ;
2025-01-18 14:42:31 +00:00
if ( lc == NULL ) {
2025-01-21 09:32:35 +00:00
completionCallback ( ls -> buf , & ctable );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
refreshLineWithFlags ( ls , flags );
2025-01-18 14:42:31 +00:00
ls -> len = saved . len ;
ls -> pos = saved . pos ;
ls -> buf = saved . buf ;
} else {
2025-01-21 09:32:35 +00:00
refreshLineWithFlags ( ls , flags );
2025-01-18 14:42:31 +00:00
}
2025-01-21 09:32:35 +00:00
if ( lc == & ctable ) {
ctable . to_free = false ;
}
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
linenoiseCompletions lc ;
2025-01-18 14:42:31 +00:00
int nwritten ;
char c = keypressed ;
2025-01-21 09:32:35 +00:00
completionCallback ( ls -> buf , & lc );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ls -> completion_idx = ( ls -> completion_idx + 1 ) % ( lc . len + 1 );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
nwritten = snprintf ( ls -> buf , ls -> buflen , "%s" , lc . cvec [ ls -> completion_idx ]);
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
refreshLineWithCompletion ( ls , & lc , REFRESH_ALL );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
const size_t len = strlen ( str );
auto copy = std :: make_unique < char [] > ( len + 1 );
if ( ! copy ) {
2025-01-18 14:42:31 +00:00
return ;
}
2025-01-21 09:32:35 +00: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 14:42:31 +00:00
lc -> cvec = cvec ;
2025-01-21 09:32:35 +00:00
lc -> cvec [ lc -> len ++ ] = copy . release ();
2025-01-18 14:42:31 +00:00
}
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
* to the right of the prompt. */
2025-01-21 09:32:35 +00:00
static void refreshShowHints ( std :: string & ab , struct linenoiseState * l , int plen ) {
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ab . append ( seq );
ab . append ( hint , hintlen );
2025-01-18 14:42:31 +00:00
if ( color != - 1 || bold != 0 )
2025-01-21 09:32:35 +00:00
ab . append ( " \033 [0m" );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
std :: string ab ;
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00:00
if ( flags & REFRESH_WRITE ) {
/* Write the prompt and the current buffer content */
2025-01-21 09:32:35 +00:00
ab . append ( l -> prompt );
2025-01-18 14:42:31 +00:00
if ( maskmode == 1 ) {
2025-01-21 09:32:35 +00:00
while ( len -- ) {
ab . append ( "*" );
}
2025-01-18 14:42:31 +00:00
} else {
2025-01-21 09:32:35 +00:00
ab . append ( buf , len );
2025-01-18 14:42:31 +00:00
}
/* Show hits if any. */
2025-01-21 09:32:35 +00:00
refreshShowHints ( ab , l , plen );
2025-01-18 14:42:31 +00:00
}
/* Erase to right */
snprintf ( seq , sizeof ( seq ), " \x1b [0K" );
2025-01-21 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00:00
if ( flags & REFRESH_WRITE ) {
/* Move cursor to original position. */
snprintf ( seq , sizeof ( seq ), " \r\x1b [%dC" , ( int )( pos + plen ));
2025-01-21 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00:00
}
2025-01-21 09:32:35 +00:00
( void ) ! write ( fd , ab . c_str (), ab . size ()); /* Can't recover from write error. */
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
std :: string ab ;
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00:00
}
}
if ( flags & REFRESH_ALL ) {
/* Clean the top line. */
lndebug ( "clear" );
snprintf ( seq , 64 , " \r\x1b [0K" );
2025-01-21 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00:00
}
if ( flags & REFRESH_WRITE ) {
/* Write the prompt and the current buffer content */
2025-01-21 09:32:35 +00:00
ab . append ( l -> prompt );
2025-01-18 14:42:31 +00:00
if ( maskmode == 1 ) {
2025-01-21 09:32:35 +00:00
for ( unsigned int i = 0 ; i < l -> len ; ++ i ) {
ab . append ( "*" );
}
2025-01-18 14:42:31 +00:00
} else {
2025-01-21 09:32:35 +00:00
ab . append ( l -> buf , l -> len );
2025-01-18 14:42:31 +00:00
}
/* Show hits if any. */
2025-01-21 09:32:35 +00:00
refreshShowHints ( ab , l , plen );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ab . append ( " \n " );
2025-01-18 14:42:31 +00:00
snprintf ( seq , 64 , " \r " );
2025-01-21 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
ab . append ( seq );
2025-01-18 14:42:31 +00:00
}
lndebug ( " \n " );
l -> oldpos = l -> pos ;
2025-01-21 09:32:35 +00:00
( void ) ! write ( fd , ab . c_str (), ab . size ()); /* Can't recover from write error. */
2025-01-18 14:42:31 +00: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 4k). */
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 09:32:35 +00:00
File file ;
file . open ( filename , "w" );
2025-01-18 14:42:31 +00:00
umask ( old_umask );
2025-01-21 09:32:35 +00:00
if ( file . file == NULL ) {
return - 1 ;
}
2025-01-18 14:42:31 +00:00
chmod ( filename , S_IRUSR | S_IWUSR );
2025-01-21 09:32:35 +00:00
for ( int j = 0 ; j < history_len ; ++ j ) {
fprintf ( file . file , "%s \n " , history [ j ]);
}
2025-01-18 14:42:31 +00: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 09:32:35 +00:00
File file ;
file . open ( filename , "r" );
2025-01-18 14:42:31 +00:00
char buf [ LINENOISE_MAX_LINE ];
2025-01-21 09:32:35 +00:00
if ( file . file == NULL ) {
return - 1 ;
}
2025-01-18 14:42:31 +00:00
2025-01-21 09:32:35 +00:00
while ( fgets ( buf , LINENOISE_MAX_LINE , file . file ) != NULL ) {
2025-01-18 14:42:31 +00:00
char * p ;
p = strchr ( buf , '\r' );
if ( ! p ) p = strchr ( buf , '\n' );
if ( p ) * p = '\0' ;
linenoiseHistoryAdd ( buf );
}
return 0 ;
}
#endif