mirror of https://github.com/ipxe/ipxe.git
369 lines
8.8 KiB
C
369 lines
8.8 KiB
C
/*
|
|
* 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;
|
|
}
|