| /* |
| * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of the |
| * License, or any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| * |
| * You can also choose to distribute this program under the terms of |
| * the Unmodified Binary Distribution Licence (as given in the file |
| * COPYING.UBDL), provided that you have satisfied its requirements. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <ipxe/console.h> |
| #include <ipxe/keys.h> |
| #include <ipxe/editstring.h> |
| #include <readline/readline.h> |
| |
| /** @file |
| * |
| * Minimal readline |
| * |
| */ |
| |
| #define READLINE_MAX 1024 |
| |
| /** |
| * Synchronise console with edited string |
| * |
| * @v string Editable string |
| */ |
| static void sync_console ( struct edit_string *string ) { |
| unsigned int mod_start = string->mod_start; |
| unsigned int mod_end = string->mod_end; |
| unsigned int cursor = string->last_cursor; |
| size_t len = strlen ( string->buf ); |
| |
| /* Expand region back to old cursor position if applicable */ |
| if ( mod_start > string->last_cursor ) |
| mod_start = string->last_cursor; |
| |
| /* Expand region forward to new cursor position if applicable */ |
| if ( mod_end < string->cursor ) |
| mod_end = string->cursor; |
| |
| /* Backspace to start of region */ |
| while ( cursor > mod_start ) { |
| putchar ( '\b' ); |
| cursor--; |
| } |
| |
| /* Print modified region */ |
| while ( cursor < mod_end ) { |
| putchar ( ( cursor >= len ) ? ' ' : string->buf[cursor] ); |
| cursor++; |
| } |
| |
| /* Backspace to new cursor position */ |
| while ( cursor > string->cursor ) { |
| putchar ( '\b' ); |
| cursor--; |
| } |
| } |
| |
| /** |
| * 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 prefill Prefill string, or NULL for no prefill |
| * @v history History buffer, or NULL for no history |
| * @v timeout Timeout period, in ticks (0=indefinite) |
| * @ret line Line read from console (excluding terminating newline) |
| * @ret rc Return status code |
| * |
| * The returned line is allocated with malloc(); the caller must |
| * eventually call free() to release the storage. |
| */ |
| int readline_history ( const char *prompt, const char *prefill, |
| struct readline_history *history, unsigned long timeout, |
| char **line ) { |
| struct edit_string string; |
| char *buf; |
| int key; |
| int move_by; |
| const char *new_string; |
| int rc; |
| |
| /* Avoid returning uninitialised data on error */ |
| *line = NULL; |
| |
| /* Display prompt, if applicable */ |
| if ( prompt ) |
| printf ( "%s", prompt ); |
| |
| /* Ensure cursor is visible */ |
| printf ( "\033[?25h" ); |
| |
| /* Allocate buffer and initialise editable string */ |
| buf = zalloc ( READLINE_MAX ); |
| if ( ! buf ) { |
| rc = -ENOMEM; |
| goto done; |
| } |
| memset ( &string, 0, sizeof ( string ) ); |
| init_editstring ( &string, buf, READLINE_MAX ); |
| |
| /* Prefill string, if applicable */ |
| if ( prefill ) { |
| replace_string ( &string, prefill ); |
| sync_console ( &string ); |
| } |
| |
| while ( 1 ) { |
| |
| /* Get keypress */ |
| key = getkey ( timeout ); |
| if ( key < 0 ) { |
| rc = -ETIMEDOUT; |
| goto done; |
| } |
| timeout = 0; |
| |
| /* Handle keypress */ |
| key = edit_string ( &string, key ); |
| sync_console ( &string ); |
| move_by = 0; |
| switch ( key ) { |
| case CR: |
| case LF: |
| /* Shrink string (ignoring failures) */ |
| *line = realloc ( buf, |
| ( strlen ( buf ) + 1 /* NUL */ ) ); |
| if ( ! *line ) |
| *line = buf; |
| buf = NULL; |
| rc = 0; |
| goto done; |
| case CTRL_C: |
| rc = -ECANCELED; |
| 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' ); |
| free ( buf ); |
| if ( history ) { |
| if ( *line && (*line)[0] ) |
| history_append ( history, *line ); |
| history_cleanup ( history ); |
| } |
| assert ( ( rc == 0 ) ^ ( *line == NULL ) ); |
| return rc; |
| } |
| |
| /** |
| * 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 ) { |
| char *line; |
| |
| readline_history ( prompt, NULL, NULL, 0, &line ); |
| return line; |
| } |