2023-11-22 11:58:43 +00:00
/* linenoise.c -- 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 .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
2023-11-22 21:44:22 +00:00
* Copyright ( c ) 2023 - 2023 , Danila Gornushko < d3g3v3 at gmail dot com >
* Copyright ( c ) 2010 - 2023 , Salvatore Sanfilippo < antirez at gmail dot com >
2023-11-22 11:58:43 +00:00
* 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
* 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
*
*/
# include <unistd.h>
# include <stdlib.h>
# include <stdio.h>
# include <errno.h>
# include <string.h>
# include <stdlib.h>
# include <ctype.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
2023-11-22 16:08:14 +00:00
# include "linenoise.h"
2023-11-22 11:58:43 +00:00
# define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
2023-11-22 17:43:11 +00:00
# define LINENOISE_MAX_LINE 4096
2023-11-22 11:58:43 +00:00
static linenoiseCompletionCallback * completionCallback = NULL ;
static linenoiseHintsCallback * hintsCallback = NULL ;
static linenoiseFreeHintsCallback * freeHintsCallback = NULL ;
2023-11-22 18:04:22 +00:00
static int maskmode = 0 ; /* Show "***" instead of input. For passwords. */
2023-11-22 11:58:43 +00:00
static int mlmode = 0 ; /* Multi line mode. Default is single line. */
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN ;
static int history_len = 0 ;
static char * * history = NULL ;
2023-11-22 16:08:14 +00:00
2023-11-22 11:58:43 +00:00
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 */
2023-11-22 16:08:14 +00:00
ENTER = 10 , /* Enter */
2023-11-22 11:58:43 +00:00
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 */
} ;
int linenoiseHistoryAdd ( const char * line ) ;
2023-11-22 21:44:22 +00: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.
2023-11-22 11:58:43 +00:00
static void refreshLine ( struct linenoiseState * l ) ;
2023-11-22 16:08:14 +00:00
/* ======================= Low level terminal handling ====================== */
2023-11-22 14:50:57 +00:00
2023-11-22 18:04:22 +00: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 ) {
2023-11-22 17:56:22 +00:00
maskmode = 1 ;
}
2023-11-22 18:04:22 +00:00
/* Disable mask mode. */
void linenoiseMaskModeDisable ( void ) {
2023-11-22 17:56:22 +00:00
maskmode = 0 ;
}
2023-11-22 11:58:43 +00:00
/* Set if to use or not the multi line mode. */
void linenoiseSetMultiLine ( int ml ) {
mlmode = ml ;
}
/* 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 . */
2023-11-22 17:43:11 +00:00
static int getCursorPosition ( ) {
char buf [ 32 ] ;
int cols , rows ;
unsigned int i = 0 ;
/* Report cursor location */
fprintf ( stdout , " \x1b [6n " ) ;
/* Read the response: ESC [ rows ; cols R */
2023-11-22 16:08:14 +00:00
while ( i < sizeof ( buf ) - 1 ) {
2023-11-22 17:43:11 +00:00
if ( fread ( buf + i , 1 , 1 , stdin ) ! = 1 ) break ;
if ( buf [ i ] = = ' R ' ) break ;
i + + ;
2023-11-22 11:58:43 +00:00
}
buf [ i ] = ' \0 ' ;
2023-11-22 17:43:11 +00:00
/* Parse it. */
if ( buf [ 0 ] ! = ESC | | buf [ 1 ] ! = ' [ ' ) return - 1 ;
if ( sscanf ( buf + 2 , " %d;%d " , & rows , & cols ) ! = 2 ) return - 1 ;
2023-11-22 11:58:43 +00:00
return cols ;
}
/* Try to get the number of columns in the current terminal, or assume 80
* if it fails . */
2023-11-22 17:43:11 +00:00
static int getColumns ( ) {
int start , cols ;
2023-11-22 11:58:43 +00:00
/* Get the initial position so we can restore it later. */
start = getCursorPosition ( ) ;
2023-11-22 17:43:11 +00:00
if ( start = = - 1 ) goto failed ;
2023-11-22 11:58:43 +00:00
2023-11-22 17:43:11 +00:00
/* Go to right margin and get position. */
if ( fwrite ( " \x1b [999C " , 1 , 6 , stdout ) ! = 6 ) goto failed ;
2023-11-22 11:58:43 +00:00
cols = getCursorPosition ( ) ;
2023-11-22 17:43:11 +00:00
if ( cols = = - 1 ) goto failed ;
2023-11-22 11:58:43 +00:00
2023-11-22 17:43:11 +00:00
/* Restore position. */
2023-11-22 11:58:43 +00:00
if ( cols > start ) {
2023-11-22 17:43:11 +00:00
char seq [ 32 ] ;
snprintf ( seq , 32 , " \x1b [%dD " , cols - start ) ;
if ( fwrite ( seq , 1 , strlen ( seq ) , stdout ) = = - 1 ) {
2023-11-22 11:58:43 +00:00
/* Can't recover... */
}
}
return cols ;
failed :
return 80 ;
}
/* Clear the screen. Used to handle ctrl+l */
void linenoiseClearScreen ( void ) {
2023-11-22 16:08:14 +00:00
fprintf ( stdout , " \x1b [H \x1b [2J " ) ;
2023-11-22 11:58:43 +00:00
}
/* Beep, used for completion when there is nothing to complete or when all
* the choices were already shown . */
static void linenoiseBeep ( void ) {
2023-11-22 14:54:51 +00:00
fprintf ( stdout , " \ x7 " ) ;
2023-11-22 11:58:43 +00:00
}
/* ============================== Completion ================================ */
/* 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 ) ;
}
2023-11-22 20:59:51 +00:00
/* This is an helper function for linenoiseEdit*() and is called when the
2023-11-22 11:58:43 +00: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
2023-11-22 16:08:14 +00:00
* structure as described in the structure definition . */
static int completeLine ( struct linenoiseState * ls ) {
2023-11-22 11:58:43 +00:00
linenoiseCompletions lc = { 0 , NULL } ;
2023-11-22 16:08:14 +00:00
int nread , nwritten ;
char c = 0 ;
2023-11-22 11:58:43 +00:00
completionCallback ( ls - > buf , & lc ) ;
if ( lc . len = = 0 ) {
linenoiseBeep ( ) ;
} else {
2023-11-22 16:08:14 +00:00
size_t stop = 0 , i = 0 ;
while ( ! stop ) {
/* Show completion or original buffer */
if ( i < lc . len ) {
struct linenoiseState saved = * ls ;
ls - > len = ls - > pos = strlen ( lc . cvec [ i ] ) ;
ls - > buf = lc . cvec [ i ] ;
refreshLine ( ls ) ;
ls - > len = saved . len ;
ls - > pos = saved . pos ;
ls - > buf = saved . buf ;
} else {
refreshLine ( ls ) ;
}
2023-11-22 11:58:43 +00:00
2023-11-22 17:43:11 +00:00
nread = fread ( & c , 1 , 1 , stdin ) ;
2023-11-22 16:08:14 +00:00
if ( nread < = 0 ) {
freeCompletions ( & lc ) ;
return - 1 ;
}
switch ( c ) {
case TAB : /* tab */
i = ( i + 1 ) % ( lc . len + 1 ) ;
if ( i = = lc . len ) linenoiseBeep ( ) ;
break ;
case ESC : /* escape */
/* Re-show original buffer */
if ( i < lc . len ) refreshLine ( ls ) ;
stop = 1 ;
break ;
default :
/* Update buffer and return */
if ( i < lc . len ) {
nwritten = snprintf ( ls - > buf , ls - > buflen , " %s " , lc . cvec [ i ] ) ;
ls - > len = ls - > pos = nwritten ;
}
stop = 1 ;
break ;
}
2023-11-22 11:58:43 +00:00
}
}
freeCompletions ( & lc ) ;
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 ) {
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 ) ;
}
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
* to the right of the prompt . */
void refreshShowHints ( struct abuf * ab , struct linenoiseState * l , int plen ) {
char seq [ 64 ] ;
if ( hintsCallback & & plen + l - > len < l - > cols ) {
int color = - 1 , bold = 0 ;
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 ;
2023-11-22 17:43:11 +00:00
if ( color ! = - 1 | | bold ! = 0 )
2023-11-22 11:58:43 +00:00
snprintf ( seq , 64 , " \033 [%d;%d;49m " , bold , color ) ;
2023-11-22 17:46:15 +00:00
else
seq [ 0 ] = ' \0 ' ;
2023-11-22 17:43:11 +00:00
abAppend ( ab , seq , strlen ( seq ) ) ;
2023-11-22 11:58:43 +00:00
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 ) ;
}
}
}
/* Single line low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content ,
2023-11-22 21:44:22 +00: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 ) {
2023-11-22 11:58:43 +00:00
char seq [ 64 ] ;
2023-11-22 16:08:14 +00:00
size_t plen = l - > plen ;
2023-11-22 11:58:43 +00:00
char * buf = l - > buf ;
size_t len = l - > len ;
size_t pos = l - > pos ;
struct abuf ab ;
while ( ( plen + pos ) > = l - > cols ) {
buf + + ;
len - - ;
pos - - ;
}
while ( plen + len > l - > cols ) {
len - - ;
}
abInit ( & ab ) ;
/* Cursor to left edge */
2023-11-22 21:44:22 +00:00
snprintf ( seq , sizeof ( seq ) , " \r " ) ;
2023-11-22 11:58:43 +00:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
2023-11-22 21:44:22 +00: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 , plen ) ;
2023-11-22 18:04:22 +00:00
}
2023-11-22 11:58:43 +00:00
/* Erase to right */
2023-11-22 21:44:22 +00:00
snprintf ( seq , sizeof ( seq ) , " \x1b [0K " ) ;
2023-11-22 16:08:14 +00:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
2023-11-22 21:44:22 +00:00
if ( flags & REFRESH_WRITE ) {
/* Move cursor to original position. */
snprintf ( seq , sizeof ( seq ) , " \r \x1b [%dC " , ( int ) ( pos + plen ) ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
2023-11-22 17:43:11 +00:00
if ( fwrite ( ab . b , ab . len , 1 , stdout ) = = - 1 ) { } /* Can't recover from write error. */
2023-11-22 11:58:43 +00:00
abFree ( & ab ) ;
}
/* Multi line low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content ,
2023-11-22 21:44:22 +00: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 ) {
2023-11-22 11:58:43 +00:00
char seq [ 64 ] ;
2023-11-22 16:08:14 +00:00
int plen = l - > plen ;
2023-11-22 11:58:43 +00:00
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. */
2023-11-22 16:08:14 +00:00
int old_rows = l - > maxrows ;
int j ;
2023-11-22 11:58:43 +00:00
struct abuf ab ;
2023-11-22 16:08:14 +00:00
/* Update maxrows if needed. */
if ( rows > ( int ) l - > maxrows ) l - > maxrows = rows ;
2023-11-22 11:58:43 +00:00
/* First step: clear all the lines used before. To do so start by
* going to the last row . */
abInit ( & ab ) ;
2023-11-22 21:44:22 +00:00
if ( flags & REFRESH_CLEAN ) {
if ( old_rows - rpos > 0 ) {
snprintf ( seq , 64 , " \x1b [%dB " , old_rows - rpos ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
2023-11-22 11:58:43 +00:00
2023-11-22 21:44:22 +00:00
/* Now for every row clear it, go up. */
for ( j = 0 ; j < old_rows - 1 ; j + + ) {
snprintf ( seq , 64 , " \r \x1b [0K \x1b [1A " ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
2023-11-22 21:48:40 +00:00
}
2023-11-22 11:58:43 +00:00
2023-11-22 21:48:40 +00:00
if ( flags & REFRESH_ALL ) {
2023-11-22 21:44:22 +00:00
/* Clean the top line. */
snprintf ( seq , 64 , " \r \x1b [0K " ) ;
2023-11-22 16:08:14 +00:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
2023-11-22 11:58:43 +00:00
2023-11-22 21:44:22 +00:00
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 , plen ) ;
/* 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 )
{
abAppend ( & ab , " \n " , 1 ) ;
snprintf ( seq , 64 , " \r " ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
rows + + ;
if ( rows > ( int ) l - > maxrows ) l - > maxrows = rows ;
}
2023-11-22 14:50:57 +00:00
2023-11-22 21:44:22 +00:00
/* Move cursor to right position. */
rpos2 = ( plen + l - > pos + l - > cols ) / l - > cols ; /* Current cursor relative row */
/* Go up till we reach the expected positon. */
if ( rows - rpos2 > 0 ) {
snprintf ( seq , 64 , " \x1b [%dA " , rows - rpos2 ) ;
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
/* Set column. */
col = ( plen + ( int ) l - > pos ) % ( int ) l - > cols ;
if ( col )
snprintf ( seq , 64 , " \r \x1b [%dC " , col ) ;
else
snprintf ( seq , 64 , " \r " ) ;
2023-11-22 11:58:43 +00:00
abAppend ( & ab , seq , strlen ( seq ) ) ;
}
l - > oldpos = l - > pos ;
2023-11-22 21:44:22 +00:00
if ( fwrite ( ab . b , ab . len , 1 , stdout ) = = - 1 ) { } /* Can't recover from write error. */
2023-11-22 11:58:43 +00:00
abFree ( & ab ) ;
}
/* Calls the two low level functions refreshSingleLine() or
* refreshMultiLine ( ) according to the selected mode . */
static void refreshLine ( struct linenoiseState * l ) {
if ( mlmode )
2023-11-22 21:44:22 +00:00
refreshMultiLine ( l , REFRESH_ALL ) ;
else
refreshSingleLine ( l , REFRESH_ALL ) ;
}
/* Hide the current line, when using the multiplexing API. */
void linenoiseHide ( struct linenoiseState * l ) {
if ( mlmode )
refreshMultiLine ( l , REFRESH_CLEAN ) ;
2023-11-22 11:58:43 +00:00
else
2023-11-22 21:44:22 +00:00
refreshSingleLine ( l , REFRESH_CLEAN ) ;
}
/* Show the current line, when using the multiplexing API. */
void linenoiseShow ( struct linenoiseState * l ) {
if ( mlmode )
refreshMultiLine ( l , REFRESH_WRITE ) ;
else
refreshSingleLine ( l , REFRESH_WRITE ) ;
2023-11-22 11:58:43 +00:00
}
/* Insert the character 'c' at cursor current position.
*
* On error writing to the terminal - 1 is returned , otherwise 0. */
int linenoiseEditInsert ( struct linenoiseState * l , char c ) {
if ( l - > len < l - > buflen ) {
if ( l - > len = = l - > pos ) {
l - > buf [ l - > pos ] = c ;
l - > pos + + ;
l - > len + + ;
l - > buf [ l - > len ] = ' \0 ' ;
if ( ( ! mlmode & & l - > plen + l - > len < l - > cols & & ! hintsCallback ) ) {
/* Avoid a full update of the line in the
* trivial case . */
2023-11-22 18:08:37 +00:00
char d = ( maskmode = = 1 ) ? ' * ' : c ;
if ( fwrite ( & d , 1 , 1 , stdout ) = = - 1 ) return - 1 ;
2023-11-22 11:58:43 +00:00
} else {
refreshLine ( l ) ;
}
} else {
memmove ( l - > buf + l - > pos + 1 , l - > buf + l - > pos , l - > len - l - > pos ) ;
l - > buf [ l - > pos ] = c ;
l - > len + + ;
l - > pos + + ;
l - > buf [ l - > len ] = ' \0 ' ;
refreshLine ( l ) ;
}
}
return 0 ;
}
/* Move cursor on the left. */
void linenoiseEditMoveLeft ( struct linenoiseState * l ) {
if ( l - > pos > 0 ) {
l - > pos - - ;
refreshLine ( l ) ;
}
}
/* Move cursor on the right. */
void linenoiseEditMoveRight ( struct linenoiseState * l ) {
if ( l - > pos ! = l - > len ) {
l - > pos + + ;
refreshLine ( l ) ;
}
}
/* Move cursor to the start of the line. */
void linenoiseEditMoveHome ( struct linenoiseState * l ) {
if ( l - > pos ! = 0 ) {
l - > pos = 0 ;
refreshLine ( l ) ;
}
}
/* Move cursor to the end of the line. */
void linenoiseEditMoveEnd ( struct linenoiseState * l ) {
if ( l - > pos ! = l - > len ) {
l - > pos = l - > len ;
refreshLine ( l ) ;
}
}
/* Substitute the currently edited line with the next or previous history
* entry as specified by ' dir ' . */
# define LINENOISE_HISTORY_NEXT 0
# define LINENOISE_HISTORY_PREV 1
void linenoiseEditHistoryNext ( struct linenoiseState * l , int dir ) {
if ( history_len > 1 ) {
/* Update the current history entry before to
* overwrite it with the next one . */
free ( history [ history_len - 1 - l - > history_index ] ) ;
history [ history_len - 1 - l - > history_index ] = strdup ( l - > buf ) ;
/* Show the new entry */
l - > history_index + = ( dir = = LINENOISE_HISTORY_PREV ) ? 1 : - 1 ;
if ( l - > history_index < 0 ) {
l - > history_index = 0 ;
return ;
} else if ( l - > history_index > = history_len ) {
l - > history_index = history_len - 1 ;
return ;
}
strncpy ( l - > buf , history [ history_len - 1 - l - > history_index ] , l - > buflen ) ;
l - > buf [ l - > buflen - 1 ] = ' \0 ' ;
l - > len = l - > pos = strlen ( l - > buf ) ;
refreshLine ( l ) ;
}
}
/* Delete the character at the right of the cursor without altering the cursor
* position . Basically this is what happens with the " Delete " keyboard key . */
void linenoiseEditDelete ( struct linenoiseState * l ) {
if ( l - > len > 0 & & l - > pos < l - > len ) {
memmove ( l - > buf + l - > pos , l - > buf + l - > pos + 1 , l - > len - l - > pos - 1 ) ;
l - > len - - ;
l - > buf [ l - > len ] = ' \0 ' ;
refreshLine ( l ) ;
}
}
/* Backspace implementation. */
void linenoiseEditBackspace ( struct linenoiseState * l ) {
if ( l - > pos > 0 & & l - > len > 0 ) {
memmove ( l - > buf + l - > pos - 1 , l - > buf + l - > pos , l - > len - l - > pos ) ;
l - > pos - - ;
l - > len - - ;
l - > buf [ l - > len ] = ' \0 ' ;
refreshLine ( l ) ;
}
}
/* Delete the previosu word, maintaining the cursor at the start of the
* 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 ;
refreshLine ( l ) ;
}
2023-11-22 20:59:51 +00: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 :
2023-11-22 14:50:57 +00:00
*
2023-11-22 20:59:51 +00: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 .
2023-11-22 14:50:57 +00:00
*
2023-11-22 20:59:51 +00:00
* 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 , char * buf , size_t buflen , const char * prompt ) {
/* Populate the linenoise state that we pass to functions implementing
* specific editing functionalities . */
l - > buf = buf ;
l - > buflen = buflen ;
l - > prompt = prompt ;
l - > plen = strlen ( prompt ) ;
l - > oldpos = l - > pos = 0 ;
l - > len = 0 ;
l - > cols = getColumns ( ) ;
l - > maxrows = 0 ;
l - > history_index = 0 ;
/* Buffer starts empty. */
l - > buf [ 0 ] = ' \0 ' ;
l - > buflen - - ; /* Make sure there is always space for the nulterm */
2023-11-22 16:08:14 +00:00
2023-11-22 20:59:51 +00:00
/* The latest history entry is always our current buffer, that
* initially is just an empty string . */
linenoiseHistoryAdd ( " " ) ;
2023-11-22 21:44:22 +00:00
int pos1 = getCursorPosition ( ) ;
2023-11-22 20:59:51 +00:00
if ( fwrite ( prompt , l - > plen , 1 , stdout ) = = - 1 ) return - 1 ;
2023-11-22 21:44:22 +00:00
int pos2 = getCursorPosition ( ) ;
if ( pos1 > = 0 & & pos2 > = 0 ) {
l - > plen = pos2 - pos1 ;
}
2023-11-22 20:59:51 +00:00
return 0 ;
}
2023-11-22 22:07:52 +00:00
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. " ;
2023-11-22 20:59:51 +00:00
/* 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 .
*
2023-11-22 22:07:52 +00:00
* 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 :
2023-11-22 20:59:51 +00:00
*
* EAGAIN if the user pressed Ctrl - C
* ENOENT if the user pressed Ctrl - D
2023-11-22 22:07:52 +00:00
*
* Some other errno : I / O error .
2023-11-22 20:59:51 +00:00
*/
2023-11-22 22:07:52 +00:00
char * linenoiseEditFeed ( struct linenoiseState * l ) {
2023-11-22 20:59:51 +00:00
char c ;
int nread ;
char seq [ 3 ] ;
nread = fread ( & c , 1 , 1 , stdin ) ;
2023-11-22 22:07:52 +00:00
if ( nread < = 0 ) return NULL ;
2023-11-22 20:59:51 +00:00
/* 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 ( c = = 9 & & completionCallback ! = NULL ) {
c = completeLine ( l ) ;
/* Return on errors */
2023-11-22 22:07:52 +00:00
if ( c < 0 ) return NULL ;
2023-11-22 20:59:51 +00:00
/* Read next character when 0 */
2023-11-22 22:07:52 +00:00
if ( c = = 0 ) return linenoiseEditMore ;
2023-11-22 20:59:51 +00:00
}
2023-11-22 16:08:14 +00:00
2023-11-22 20:59:51 +00: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 ;
}
2023-11-22 22:07:52 +00:00
return strdup ( l - > buf ) ;
2023-11-22 20:59:51 +00:00
case CTRL_C : /* ctrl-c */
errno = EAGAIN ;
2023-11-22 22:07:52 +00:00
return NULL ;
2023-11-22 20:59:51 +00:00
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 {
2023-11-22 11:58:43 +00:00
history_len - - ;
free ( history [ history_len ] ) ;
2023-11-22 20:59:51 +00:00
errno = ENOENT ;
2023-11-22 22:07:52 +00:00
return NULL ;
2023-11-22 20:59:51 +00:00
}
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.
* chars at different times . */
if ( fread ( seq , 1 , 1 , stdin ) = = - 1 ) break ;
if ( fread ( seq + 1 , 1 , 1 , stdin ) = = - 1 ) break ;
// or just if (fread(seq, 1, 2, stdin) < 2) break;
/* ESC [ sequences. */
if ( seq [ 0 ] = = ' [ ' ) {
if ( seq [ 1 ] > = ' 0 ' & & seq [ 1 ] < = ' 9 ' ) {
/* Extended escape, read additional byte. */
if ( fread ( seq + 2 , 1 , 1 , stdin ) = = - 1 ) break ;
if ( seq [ 2 ] = = ' ~ ' ) {
2023-11-22 11:58:43 +00:00
switch ( seq [ 1 ] ) {
2023-11-22 20:59:51 +00:00
case ' 3 ' : /* Delete key. */
linenoiseEditDelete ( l ) ;
2023-11-22 11:58:43 +00:00
break ;
}
}
2023-11-22 20:59:51 +00:00
} else {
2023-11-22 11:58:43 +00:00
switch ( seq [ 1 ] ) {
2023-11-22 20:59:51 +00: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 ;
2023-11-22 11:58:43 +00:00
case ' H ' : /* Home */
2023-11-22 20:59:51 +00:00
linenoiseEditMoveHome ( l ) ;
2023-11-22 11:58:43 +00:00
break ;
case ' F ' : /* End*/
2023-11-22 20:59:51 +00:00
linenoiseEditMoveEnd ( l ) ;
2023-11-22 11:58:43 +00:00
break ;
}
}
}
2023-11-22 20:59:51 +00: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 :
2023-11-22 22:07:52 +00:00
if ( linenoiseEditInsert ( l , c ) ) return NULL ;
2023-11-22 20:59:51 +00:00
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 ;
2023-11-22 11:58:43 +00:00
}
2023-11-22 22:07:52 +00:00
return linenoiseEditMore ;
2023-11-22 11:58:43 +00:00
}
2023-11-22 20:59:51 +00:00
/* 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 ) {
printf ( " \n " ) ;
}
2023-11-22 14:50:57 +00:00
2023-11-22 20:59:51 +00:00
/* 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 . */
2023-11-22 22:07:52 +00:00
static char * linenoiseBlockingEdit ( char * buf , size_t buflen , const char * prompt )
2023-11-22 20:59:51 +00:00
{
2023-11-22 22:07:52 +00:00
struct linenoiseState l ;
2023-11-22 20:59:51 +00:00
/* Editing without a buffer is invalid. */
2023-11-22 16:08:14 +00:00
if ( buflen = = 0 ) {
errno = EINVAL ;
2023-11-22 22:07:52 +00:00
return NULL ;
2023-11-22 14:50:57 +00:00
}
2023-11-22 22:07:52 +00:00
linenoiseEditStart ( & l , buf , buflen , prompt ) ;
char * res ;
while ( ( res = linenoiseEditFeed ( & l ) ) = = linenoiseEditMore ) ;
linenoiseEditStop ( & l ) ;
return res ;
2023-11-22 16:08:14 +00:00
}
2023-11-22 14:50:57 +00:00
2023-11-22 16:08:14 +00:00
/* The high level function that is the main API of the linenoise library. */
char * linenoise ( const char * prompt ) {
2023-11-22 17:43:11 +00:00
char buf [ LINENOISE_MAX_LINE ] ;
2023-11-22 22:07:52 +00:00
char * retval = linenoiseBlockingEdit ( buf , LINENOISE_MAX_LINE , prompt ) ;
return retval ;
2023-11-22 11:58:43 +00: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 ) {
2023-11-22 22:07:52 +00:00
if ( ptr = = linenoiseEditMore ) return ; // Protect from API misuse.
2023-11-22 11:58:43 +00:00
free ( ptr ) ;
}
/* ================================ History ================================= */
2023-11-22 17:43:11 +00:00
void linenoiseHistoryFree ( ) {
2023-11-22 11:58:43 +00:00
if ( history ) {
2023-11-22 16:08:14 +00:00
for ( int j = 0 ; j < history_len ; j + + ) {
2023-11-22 11:58:43 +00:00
free ( history [ j ] ) ;
2023-11-22 16:08:14 +00:00
}
2023-11-22 11:58:43 +00:00
free ( history ) ;
}
2023-11-22 16:08:14 +00:00
history = NULL ;
2023-11-22 11:58:43 +00:00
}
/* 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 ) {
FILE * fp ;
int j ;
fp = fopen ( filename , " w " ) ;
if ( fp = = NULL ) return - 1 ;
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 ) {
2023-11-22 17:43:11 +00:00
FILE * fp = fopen ( filename , " r " ) ;
char buf [ LINENOISE_MAX_LINE ] ;
2023-11-22 11:58:43 +00:00
2023-11-22 17:43:11 +00:00
if ( fp = = NULL ) return - 1 ;
2023-11-22 11:58:43 +00:00
2023-11-22 17:43:11 +00:00
while ( fgets ( buf , LINENOISE_MAX_LINE , fp ) ! = NULL ) {
2023-11-22 11:58:43 +00:00
char * p ;
p = strchr ( buf , ' \r ' ) ;
if ( ! p ) p = strchr ( buf , ' \n ' ) ;
if ( p ) * p = ' \0 ' ;
linenoiseHistoryAdd ( buf ) ;
}
fclose ( fp ) ;
return 0 ;
}