summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMichael Brown2011-03-29 19:00:30 +0200
committerMichael Brown2011-03-30 20:43:14 +0200
commit5a064dd2c4330cae7a83aa94049bd2b649f4a71c (patch)
tree46dd9e72558b4d9d66b9b4ff33f73259efd45538 /src
parent[readline] Add replace_string() (diff)
downloadipxe-5a064dd2c4330cae7a83aa94049bd2b649f4a71c.tar.gz
ipxe-5a064dd2c4330cae7a83aa94049bd2b649f4a71c.tar.xz
ipxe-5a064dd2c4330cae7a83aa94049bd2b649f4a71c.zip
[readline] Add history support
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src')
-rw-r--r--src/hci/readline.c224
-rw-r--r--src/include/readline/readline.h43
2 files changed, 258 insertions, 9 deletions
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
*
@@ -75,44 +73,252 @@ static void sync_console ( struct edit_string *string ) {
}
/**
- * Read line from console
+ * 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 ( const char *prompt ) {
+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:
- putchar ( '\n' );
line = strdup ( buf );
if ( ! line )
- printf ( "Out of memory\n" );
- return line;
+ printf ( "\nOut of memory" );
+ goto done;
case CTRL_C:
- putchar ( '\n' );
- return NULL;
+ 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
+ *
+ * @v prompt Prompt string
+ * @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 ( const char *prompt ) {
+ 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 */