|
@@ -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
|
}
|