mirror of
https://github.com/xcat2/xNBA.git
synced 2025-02-16 02:28:24 +00:00
[readline] Add history support
Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
parent
d6f2408f2c
commit
5a064dd2c4
@ -34,8 +34,6 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
#define READLINE_MAX 256
|
||||
|
||||
static void sync_console ( struct edit_string *string ) __nonnull;
|
||||
|
||||
/**
|
||||
* Synchronise console with edited string
|
||||
*
|
||||
@ -74,6 +72,244 @@ static void sync_console ( struct edit_string *string ) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate history entry
|
||||
*
|
||||
* @v history History buffer
|
||||
* @v depth Depth within history buffer
|
||||
* @ret entry History entry
|
||||
*/
|
||||
static struct readline_history_entry *
|
||||
history_entry ( struct readline_history *history, unsigned int depth ) {
|
||||
unsigned int offset;
|
||||
|
||||
offset = ( ( history->next - depth ) %
|
||||
( sizeof ( history->entries ) /
|
||||
sizeof ( history->entries[0] ) ) );
|
||||
return &history->entries[offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read string from history buffer
|
||||
*
|
||||
* @v history History buffer
|
||||
* @v depth Depth within history buffer
|
||||
* @ret string String
|
||||
*/
|
||||
static const char * history_fetch ( struct readline_history *history,
|
||||
unsigned int depth ) {
|
||||
struct readline_history_entry *entry;
|
||||
|
||||
/* Return the temporary copy if it exists, otherwise return
|
||||
* the persistent copy.
|
||||
*/
|
||||
entry = history_entry ( history, depth );
|
||||
return ( entry->temp ? entry->temp : entry->string );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write temporary string copy to history buffer
|
||||
*
|
||||
* @v history History buffer
|
||||
* @v depth Depth within history buffer
|
||||
* @v string String
|
||||
*/
|
||||
static void history_store ( struct readline_history *history,
|
||||
unsigned int depth, const char *string ) {
|
||||
struct readline_history_entry *entry;
|
||||
char *temp;
|
||||
|
||||
/* Create temporary copy of string */
|
||||
temp = strdup ( string );
|
||||
if ( ! temp ) {
|
||||
/* Just discard the string; there's nothing we can do */
|
||||
DBGC ( history, "READLINE %p could not store string\n",
|
||||
history );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store temporary copy */
|
||||
entry = history_entry ( history, depth );
|
||||
free ( entry->temp );
|
||||
entry->temp = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to new history depth
|
||||
*
|
||||
* @v history History buffer
|
||||
* @v offset Offset by which to change depth
|
||||
* @v old_string String (possibly modified) at current depth
|
||||
* @ret new_string String at new depth, or NULL for no movement
|
||||
*/
|
||||
static const char * history_move ( struct readline_history *history,
|
||||
int offset, const char *old_string ) {
|
||||
unsigned int new_depth = ( history->depth + offset );
|
||||
const char * new_string = history_fetch ( history, new_depth );
|
||||
|
||||
/* Depth checks */
|
||||
if ( new_depth > READLINE_HISTORY_MAX_DEPTH )
|
||||
return NULL;
|
||||
if ( ! new_string )
|
||||
return NULL;
|
||||
|
||||
/* Store temporary copy of old string at current depth */
|
||||
history_store ( history, history->depth, old_string );
|
||||
|
||||
/* Update depth */
|
||||
history->depth = new_depth;
|
||||
|
||||
/* Return new string */
|
||||
return new_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append new history entry
|
||||
*
|
||||
* @v history History buffer
|
||||
* @v string String
|
||||
*/
|
||||
static void history_append ( struct readline_history *history,
|
||||
const char *string ) {
|
||||
struct readline_history_entry *entry;
|
||||
|
||||
/* Store new entry */
|
||||
entry = history_entry ( history, 0 );
|
||||
assert ( entry->string == NULL );
|
||||
entry->string = strdup ( string );
|
||||
if ( ! entry->string ) {
|
||||
/* Just discard the string; there's nothing we can do */
|
||||
DBGC ( history, "READLINE %p could not append string\n",
|
||||
history );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Increment history position */
|
||||
history->next++;
|
||||
|
||||
/* Prepare empty "next" slot */
|
||||
entry = history_entry ( history, 0 );
|
||||
free ( entry->string );
|
||||
entry->string = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up history after editing
|
||||
*
|
||||
* @v history History buffer
|
||||
*/
|
||||
static void history_cleanup ( struct readline_history *history ) {
|
||||
struct readline_history_entry *entry;
|
||||
unsigned int i;
|
||||
|
||||
/* Discard any temporary strings */
|
||||
for ( i = 0 ; i < ( sizeof ( history->entries ) /
|
||||
sizeof ( history->entries[0] ) ) ; i++ ) {
|
||||
entry = &history->entries[i];
|
||||
free ( entry->temp );
|
||||
entry->temp = NULL;
|
||||
}
|
||||
|
||||
/* Reset depth */
|
||||
history->depth = 0;
|
||||
|
||||
/* Sanity check */
|
||||
entry = history_entry ( history, 0 );
|
||||
assert ( entry->string == NULL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Free history buffer
|
||||
*
|
||||
* @v history History buffer
|
||||
*/
|
||||
void history_free ( struct readline_history *history ) {
|
||||
struct readline_history_entry *entry;
|
||||
unsigned int i;
|
||||
|
||||
/* Discard any temporary strings */
|
||||
for ( i = 0 ; i < ( sizeof ( history->entries ) /
|
||||
sizeof ( history->entries[0] ) ) ; i++ ) {
|
||||
entry = &history->entries[i];
|
||||
assert ( entry->temp == NULL );
|
||||
free ( entry->string );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read line from console (with history)
|
||||
*
|
||||
* @v prompt Prompt string
|
||||
* @v history History buffer, or NULL for no history
|
||||
* @ret line Line read from console (excluding terminating newline)
|
||||
*
|
||||
* The returned line is allocated with malloc(); the caller must
|
||||
* eventually call free() to release the storage.
|
||||
*/
|
||||
char * readline_history ( const char *prompt,
|
||||
struct readline_history *history ) {
|
||||
char buf[READLINE_MAX];
|
||||
struct edit_string string;
|
||||
int key;
|
||||
int move_by;
|
||||
const char *new_string;
|
||||
char *line;
|
||||
|
||||
/* Display prompt, if applicable */
|
||||
if ( prompt )
|
||||
printf ( "%s", prompt );
|
||||
|
||||
/* Initialise editable string */
|
||||
memset ( &string, 0, sizeof ( string ) );
|
||||
init_editstring ( &string, buf, sizeof ( buf ) );
|
||||
buf[0] = '\0';
|
||||
|
||||
while ( 1 ) {
|
||||
/* Handle keypress */
|
||||
key = edit_string ( &string, getkey ( 0 ) );
|
||||
sync_console ( &string );
|
||||
move_by = 0;
|
||||
switch ( key ) {
|
||||
case CR:
|
||||
case LF:
|
||||
line = strdup ( buf );
|
||||
if ( ! line )
|
||||
printf ( "\nOut of memory" );
|
||||
goto done;
|
||||
case CTRL_C:
|
||||
line = NULL;
|
||||
goto done;
|
||||
case KEY_UP:
|
||||
move_by = 1;
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
move_by = -1;
|
||||
break;
|
||||
default:
|
||||
/* Do nothing */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle history movement, if applicable */
|
||||
if ( move_by && history ) {
|
||||
new_string = history_move ( history, move_by, buf );
|
||||
if ( new_string ) {
|
||||
replace_string ( &string, new_string );
|
||||
sync_console ( &string );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
putchar ( '\n' );
|
||||
if ( history ) {
|
||||
if ( line && line[0] )
|
||||
history_append ( history, line );
|
||||
history_cleanup ( history );
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read line from console
|
||||
*
|
||||
@ -84,35 +320,5 @@ static void sync_console ( struct edit_string *string ) {
|
||||
* eventually call free() to release the storage.
|
||||
*/
|
||||
char * readline ( const char *prompt ) {
|
||||
char buf[READLINE_MAX];
|
||||
struct edit_string string;
|
||||
int key;
|
||||
char *line;
|
||||
|
||||
if ( prompt )
|
||||
printf ( "%s", prompt );
|
||||
|
||||
memset ( &string, 0, sizeof ( string ) );
|
||||
init_editstring ( &string, buf, sizeof ( buf ) );
|
||||
buf[0] = '\0';
|
||||
|
||||
while ( 1 ) {
|
||||
key = edit_string ( &string, getkey ( 0 ) );
|
||||
sync_console ( &string );
|
||||
switch ( key ) {
|
||||
case CR:
|
||||
case LF:
|
||||
putchar ( '\n' );
|
||||
line = strdup ( buf );
|
||||
if ( ! line )
|
||||
printf ( "Out of memory\n" );
|
||||
return line;
|
||||
case CTRL_C:
|
||||
putchar ( '\n' );
|
||||
return NULL;
|
||||
default:
|
||||
/* Do nothing */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return readline_history ( prompt, NULL );
|
||||
}
|
||||
|
@ -9,6 +9,49 @@
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER );
|
||||
|
||||
/** A readline history entry */
|
||||
struct readline_history_entry {
|
||||
/** Persistent copy of string */
|
||||
char *string;
|
||||
/** Temporary copy of string
|
||||
*
|
||||
* The temporary copy exists only during the call to
|
||||
* readline().
|
||||
*/
|
||||
char *temp;
|
||||
};
|
||||
|
||||
/** Maximum depth of a readline history buffer
|
||||
*
|
||||
* Must be one less than a power of two.
|
||||
*/
|
||||
#define READLINE_HISTORY_MAX_DEPTH ( ( 1 << 3 ) - 1 )
|
||||
|
||||
/** A readline history buffer */
|
||||
struct readline_history {
|
||||
/** History entries
|
||||
*
|
||||
* This is a circular buffer, with entries in chronological
|
||||
* order. The "next" entry is always empty except during a
|
||||
* call to readline().
|
||||
*/
|
||||
struct readline_history_entry entries[READLINE_HISTORY_MAX_DEPTH + 1];
|
||||
/** Position of next entry within buffer
|
||||
*
|
||||
* This is incremented monotonically each time an entry is
|
||||
* added to the buffer.
|
||||
*/
|
||||
unsigned int next;
|
||||
/** Current depth within history buffer
|
||||
*
|
||||
* This is valid only during the call to readline()
|
||||
*/
|
||||
unsigned int depth;
|
||||
};
|
||||
|
||||
extern void history_free ( struct readline_history *history );
|
||||
extern char * __malloc readline_history ( const char *prompt,
|
||||
struct readline_history *history );
|
||||
extern char * __malloc readline ( const char *prompt );
|
||||
|
||||
#endif /* _READLINE_H */
|
||||
|
Loading…
x
Reference in New Issue
Block a user