diff --git a/src/hci/readline.c b/src/hci/readline.c index 666ebf0c..32793abe 100644 --- a/src/hci/readline.c +++ b/src/hci/readline.c @@ -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 ); } diff --git a/src/include/readline/readline.h b/src/include/readline/readline.h index 700b7aa2..42dfd8c4 100644 --- a/src/include/readline/readline.h +++ b/src/include/readline/readline.h @@ -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 */