]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[readline] Add history support
authorMichael Brown <mcb30@ipxe.org>
Tue, 29 Mar 2011 17:00:30 +0000 (18:00 +0100)
committerMichael Brown <mcb30@ipxe.org>
Wed, 30 Mar 2011 18:43:14 +0000 (19:43 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/hci/readline.c
src/include/readline/readline.h

index 666ebf0c46b4ff864b59408ebc3d6e81e1633e33..32793abe5635e655f9c403335b3319741830fc1e 100644 (file)
@@ -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 );
 }
index 700b7aa2e60f9fde1d378d7797bed8b433bd6655..42dfd8c4cf6daf3e35ad477d748257c7f5a939f3 100644 (file)
@@ -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 */