Browse Source

[readline] Add history support

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 13 years ago
parent
commit
5a064dd2c4
2 changed files with 258 additions and 9 deletions
  1. 215
    9
      src/hci/readline.c
  2. 43
    0
      src/include/readline/readline.h

+ 215
- 9
src/hci/readline.c View File

@@ -34,8 +34,6 @@ FILE_LICENCE ( GPL2_OR_LATER );
34 34
 
35 35
 #define READLINE_MAX 256
36 36
 
37
-static void sync_console ( struct edit_string *string ) __nonnull;
38
-
39 37
 /**
40 38
  * Synchronise console with edited string
41 39
  *
@@ -75,44 +73,252 @@ static void sync_console ( struct edit_string *string ) {
75 73
 }
76 74
 
77 75
 /**
78
- * Read line from console
76
+ * Locate history entry
77
+ *
78
+ * @v history		History buffer
79
+ * @v depth		Depth within history buffer
80
+ * @ret entry		History entry
81
+ */
82
+static struct readline_history_entry *
83
+history_entry ( struct readline_history *history, unsigned int depth ) {
84
+	unsigned int offset;
85
+
86
+	offset = ( ( history->next - depth ) %
87
+		   ( sizeof ( history->entries ) /
88
+		     sizeof ( history->entries[0] ) ) );
89
+	return &history->entries[offset];
90
+}
91
+
92
+/**
93
+ * Read string from history buffer
94
+ *
95
+ * @v history		History buffer
96
+ * @v depth		Depth within history buffer
97
+ * @ret string		String
98
+ */
99
+static const char * history_fetch ( struct readline_history *history,
100
+				    unsigned int depth ) {
101
+	struct readline_history_entry *entry;
102
+
103
+	/* Return the temporary copy if it exists, otherwise return
104
+	 * the persistent copy.
105
+	 */
106
+	entry = history_entry ( history, depth );
107
+	return ( entry->temp ? entry->temp : entry->string );
108
+}
109
+
110
+/**
111
+ * Write temporary string copy to history buffer
112
+ *
113
+ * @v history		History buffer
114
+ * @v depth		Depth within history buffer
115
+ * @v string		String
116
+ */
117
+static void history_store ( struct readline_history *history,
118
+			    unsigned int depth, const char *string ) {
119
+	struct readline_history_entry *entry;
120
+	char *temp;
121
+
122
+	/* Create temporary copy of string */
123
+	temp = strdup ( string );
124
+	if ( ! temp ) {
125
+		/* Just discard the string; there's nothing we can do */
126
+		DBGC ( history, "READLINE %p could not store string\n",
127
+		       history );
128
+		return;
129
+	}
130
+
131
+	/* Store temporary copy */
132
+	entry = history_entry ( history, depth );
133
+	free ( entry->temp );
134
+	entry->temp = temp;
135
+}
136
+
137
+/**
138
+ * Move to new history depth
139
+ *
140
+ * @v history		History buffer
141
+ * @v offset		Offset by which to change depth
142
+ * @v old_string	String (possibly modified) at current depth
143
+ * @ret new_string	String at new depth, or NULL for no movement
144
+ */
145
+static const char * history_move ( struct readline_history *history,
146
+				   int offset, const char *old_string ) {
147
+	unsigned int new_depth = ( history->depth + offset );
148
+	const char * new_string = history_fetch ( history, new_depth );
149
+
150
+	/* Depth checks */
151
+	if ( new_depth > READLINE_HISTORY_MAX_DEPTH )
152
+		return NULL;
153
+	if ( ! new_string )
154
+		return NULL;
155
+
156
+	/* Store temporary copy of old string at current depth */
157
+	history_store ( history, history->depth, old_string );
158
+
159
+	/* Update depth */
160
+	history->depth = new_depth;
161
+
162
+	/* Return new string */
163
+	return new_string;
164
+}
165
+
166
+/**
167
+ * Append new history entry
168
+ *
169
+ * @v history		History buffer
170
+ * @v string		String
171
+ */
172
+static void history_append ( struct readline_history *history,
173
+			     const char *string ) {
174
+	struct readline_history_entry *entry;
175
+
176
+	/* Store new entry */
177
+	entry = history_entry ( history, 0 );
178
+	assert ( entry->string == NULL );
179
+	entry->string = strdup ( string );
180
+	if ( ! entry->string ) {
181
+		/* Just discard the string; there's nothing we can do */
182
+		DBGC ( history, "READLINE %p could not append string\n",
183
+		       history );
184
+		return;
185
+	}
186
+
187
+	/* Increment history position */
188
+	history->next++;
189
+
190
+	/* Prepare empty "next" slot */
191
+	entry = history_entry ( history, 0 );
192
+	free ( entry->string );
193
+	entry->string = NULL;
194
+}
195
+
196
+/**
197
+ * Clean up history after editing
198
+ *
199
+ * @v history		History buffer
200
+ */
201
+static void history_cleanup ( struct readline_history *history ) {
202
+	struct readline_history_entry *entry;
203
+	unsigned int i;
204
+
205
+	/* Discard any temporary strings */
206
+	for ( i = 0 ; i < ( sizeof ( history->entries ) /
207
+			    sizeof ( history->entries[0] ) ) ; i++ ) {
208
+		entry = &history->entries[i];
209
+		free ( entry->temp );
210
+		entry->temp = NULL;
211
+	}
212
+
213
+	/* Reset depth */
214
+	history->depth = 0;
215
+
216
+	/* Sanity check */
217
+	entry = history_entry ( history, 0 );
218
+	assert ( entry->string == NULL );
219
+}
220
+
221
+/**
222
+ * Free history buffer
223
+ *
224
+ * @v history		History buffer
225
+ */
226
+void history_free ( struct readline_history *history ) {
227
+	struct readline_history_entry *entry;
228
+	unsigned int i;
229
+
230
+	/* Discard any temporary strings */
231
+	for ( i = 0 ; i < ( sizeof ( history->entries ) /
232
+			    sizeof ( history->entries[0] ) ) ; i++ ) {
233
+		entry = &history->entries[i];
234
+		assert ( entry->temp == NULL );
235
+		free ( entry->string );
236
+	}
237
+}
238
+
239
+/**
240
+ * Read line from console (with history)
79 241
  *
80 242
  * @v prompt		Prompt string
243
+ * @v history		History buffer, or NULL for no history
81 244
  * @ret line		Line read from console (excluding terminating newline)
82 245
  *
83 246
  * The returned line is allocated with malloc(); the caller must
84 247
  * eventually call free() to release the storage.
85 248
  */
86
-char * readline ( const char *prompt ) {
249
+char * readline_history ( const char *prompt,
250
+			  struct readline_history *history ) {
87 251
 	char buf[READLINE_MAX];
88 252
 	struct edit_string string;
89 253
 	int key;
254
+	int move_by;
255
+	const char *new_string;
90 256
 	char *line;
91 257
 
258
+	/* Display prompt, if applicable */
92 259
 	if ( prompt )
93 260
 		printf ( "%s", prompt );
94 261
 
262
+	/* Initialise editable string */
95 263
 	memset ( &string, 0, sizeof ( string ) );
96 264
 	init_editstring ( &string, buf, sizeof ( buf ) );
97 265
 	buf[0] = '\0';
98 266
 
99 267
 	while ( 1 ) {
268
+		/* Handle keypress */
100 269
 		key = edit_string ( &string, getkey ( 0 ) );
101 270
 		sync_console ( &string );
271
+		move_by = 0;
102 272
 		switch ( key ) {
103 273
 		case CR:
104 274
 		case LF:
105
-			putchar ( '\n' );
106 275
 			line = strdup ( buf );
107 276
 			if ( ! line )
108
-				printf ( "Out of memory\n" );
109
-			return line;
277
+				printf ( "\nOut of memory" );
278
+			goto done;
110 279
 		case CTRL_C:
111
-			putchar ( '\n' );
112
-			return NULL;
280
+			line = NULL;
281
+			goto done;
282
+		case KEY_UP:
283
+			move_by = 1;
284
+			break;
285
+		case KEY_DOWN:
286
+			move_by = -1;
287
+			break;
113 288
 		default:
114 289
 			/* Do nothing */
115 290
 			break;
116 291
 		}
292
+
293
+		/* Handle history movement, if applicable */
294
+		if ( move_by && history ) {
295
+			new_string = history_move ( history, move_by, buf );
296
+			if ( new_string ) {
297
+				replace_string ( &string, new_string );
298
+				sync_console ( &string );
299
+			}
300
+		}
117 301
 	}
302
+
303
+ done:
304
+	putchar ( '\n' );
305
+	if ( history ) {
306
+		if ( line && line[0] )
307
+			history_append ( history, line );
308
+		history_cleanup ( history );
309
+	}
310
+	return line;
311
+}
312
+
313
+/**
314
+ * Read line from console
315
+ *
316
+ * @v prompt		Prompt string
317
+ * @ret line		Line read from console (excluding terminating newline)
318
+ *
319
+ * The returned line is allocated with malloc(); the caller must
320
+ * eventually call free() to release the storage.
321
+ */
322
+char * readline ( const char *prompt ) {
323
+	return readline_history ( prompt, NULL );
118 324
 }

+ 43
- 0
src/include/readline/readline.h View File

@@ -9,6 +9,49 @@
9 9
 
10 10
 FILE_LICENCE ( GPL2_OR_LATER );
11 11
 
12
+/** A readline history entry */
13
+struct readline_history_entry {
14
+	/** Persistent copy of string */
15
+	char *string;
16
+	/** Temporary copy of string
17
+	 *
18
+	 * The temporary copy exists only during the call to
19
+	 * readline().
20
+	 */
21
+	char *temp;
22
+};
23
+
24
+/** Maximum depth of a readline history buffer
25
+ *
26
+ * Must be one less than a power of two.
27
+ */
28
+#define READLINE_HISTORY_MAX_DEPTH ( ( 1 << 3 ) - 1 )
29
+
30
+/** A readline history buffer */
31
+struct readline_history {
32
+	/** History entries
33
+	 *
34
+	 * This is a circular buffer, with entries in chronological
35
+	 * order.  The "next" entry is always empty except during a
36
+	 * call to readline().
37
+	 */
38
+	struct readline_history_entry entries[READLINE_HISTORY_MAX_DEPTH + 1];
39
+	/** Position of next entry within buffer
40
+	 *
41
+	 * This is incremented monotonically each time an entry is
42
+	 * added to the buffer.
43
+	 */
44
+	unsigned int next;
45
+	/** Current depth within history buffer
46
+	 *
47
+	 * This is valid only during the call to readline()
48
+	 */
49
+	unsigned int depth;
50
+};
51
+
52
+extern void history_free ( struct readline_history *history );
53
+extern char * __malloc readline_history ( const char *prompt,
54
+					  struct readline_history *history );
12 55
 extern char * __malloc readline ( const char *prompt );
13 56
 
14 57
 #endif /* _READLINE_H */

Loading…
Cancel
Save