iPXE
readline.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdio.h>
00027 #include <string.h>
00028 #include <stdlib.h>
00029 #include <errno.h>
00030 #include <ipxe/console.h>
00031 #include <ipxe/keys.h>
00032 #include <ipxe/editstring.h>
00033 #include <readline/readline.h>
00034 
00035 /** @file
00036  *
00037  * Minimal readline
00038  *
00039  */
00040 
00041 #define READLINE_MAX 256
00042 
00043 /**
00044  * Synchronise console with edited string
00045  *
00046  * @v string            Editable string
00047  */
00048 static void sync_console ( struct edit_string *string ) {
00049         unsigned int mod_start = string->mod_start;
00050         unsigned int mod_end = string->mod_end;
00051         unsigned int cursor = string->last_cursor;
00052         size_t len = strlen ( string->buf );
00053 
00054         /* Expand region back to old cursor position if applicable */
00055         if ( mod_start > string->last_cursor )
00056                 mod_start = string->last_cursor;
00057 
00058         /* Expand region forward to new cursor position if applicable */
00059         if ( mod_end < string->cursor )
00060                 mod_end = string->cursor;
00061 
00062         /* Backspace to start of region */
00063         while ( cursor > mod_start ) {
00064                 putchar ( '\b' );
00065                 cursor--;
00066         }
00067 
00068         /* Print modified region */
00069         while ( cursor < mod_end ) {
00070                 putchar ( ( cursor >= len ) ? ' ' : string->buf[cursor] );
00071                 cursor++;
00072         }
00073 
00074         /* Backspace to new cursor position */
00075         while ( cursor > string->cursor ) {
00076                 putchar ( '\b' );
00077                 cursor--;
00078         }
00079 }
00080 
00081 /**
00082  * Locate history entry
00083  *
00084  * @v history           History buffer
00085  * @v depth             Depth within history buffer
00086  * @ret entry           History entry
00087  */
00088 static struct readline_history_entry *
00089 history_entry ( struct readline_history *history, unsigned int depth ) {
00090         unsigned int offset;
00091 
00092         offset = ( ( history->next - depth ) %
00093                    ( sizeof ( history->entries ) /
00094                      sizeof ( history->entries[0] ) ) );
00095         return &history->entries[offset];
00096 }
00097 
00098 /**
00099  * Read string from history buffer
00100  *
00101  * @v history           History buffer
00102  * @v depth             Depth within history buffer
00103  * @ret string          String
00104  */
00105 static const char * history_fetch ( struct readline_history *history,
00106                                     unsigned int depth ) {
00107         struct readline_history_entry *entry;
00108 
00109         /* Return the temporary copy if it exists, otherwise return
00110          * the persistent copy.
00111          */
00112         entry = history_entry ( history, depth );
00113         return ( entry->temp ? entry->temp : entry->string );
00114 }
00115 
00116 /**
00117  * Write temporary string copy to history buffer
00118  *
00119  * @v history           History buffer
00120  * @v depth             Depth within history buffer
00121  * @v string            String
00122  */
00123 static void history_store ( struct readline_history *history,
00124                             unsigned int depth, const char *string ) {
00125         struct readline_history_entry *entry;
00126         char *temp;
00127 
00128         /* Create temporary copy of string */
00129         temp = strdup ( string );
00130         if ( ! temp ) {
00131                 /* Just discard the string; there's nothing we can do */
00132                 DBGC ( history, "READLINE %p could not store string\n",
00133                        history );
00134                 return;
00135         }
00136 
00137         /* Store temporary copy */
00138         entry = history_entry ( history, depth );
00139         free ( entry->temp );
00140         entry->temp = temp;
00141 }
00142 
00143 /**
00144  * Move to new history depth
00145  *
00146  * @v history           History buffer
00147  * @v offset            Offset by which to change depth
00148  * @v old_string        String (possibly modified) at current depth
00149  * @ret new_string      String at new depth, or NULL for no movement
00150  */
00151 static const char * history_move ( struct readline_history *history,
00152                                    int offset, const char *old_string ) {
00153         unsigned int new_depth = ( history->depth + offset );
00154         const char * new_string = history_fetch ( history, new_depth );
00155 
00156         /* Depth checks */
00157         if ( new_depth > READLINE_HISTORY_MAX_DEPTH )
00158                 return NULL;
00159         if ( ! new_string )
00160                 return NULL;
00161 
00162         /* Store temporary copy of old string at current depth */
00163         history_store ( history, history->depth, old_string );
00164 
00165         /* Update depth */
00166         history->depth = new_depth;
00167 
00168         /* Return new string */
00169         return new_string;
00170 }
00171 
00172 /**
00173  * Append new history entry
00174  *
00175  * @v history           History buffer
00176  * @v string            String
00177  */
00178 static void history_append ( struct readline_history *history,
00179                              const char *string ) {
00180         struct readline_history_entry *entry;
00181 
00182         /* Store new entry */
00183         entry = history_entry ( history, 0 );
00184         assert ( entry->string == NULL );
00185         entry->string = strdup ( string );
00186         if ( ! entry->string ) {
00187                 /* Just discard the string; there's nothing we can do */
00188                 DBGC ( history, "READLINE %p could not append string\n",
00189                        history );
00190                 return;
00191         }
00192 
00193         /* Increment history position */
00194         history->next++;
00195 
00196         /* Prepare empty "next" slot */
00197         entry = history_entry ( history, 0 );
00198         free ( entry->string );
00199         entry->string = NULL;
00200 }
00201 
00202 /**
00203  * Clean up history after editing
00204  *
00205  * @v history           History buffer
00206  */
00207 static void history_cleanup ( struct readline_history *history ) {
00208         struct readline_history_entry *entry;
00209         unsigned int i;
00210 
00211         /* Discard any temporary strings */
00212         for ( i = 0 ; i < ( sizeof ( history->entries ) /
00213                             sizeof ( history->entries[0] ) ) ; i++ ) {
00214                 entry = &history->entries[i];
00215                 free ( entry->temp );
00216                 entry->temp = NULL;
00217         }
00218 
00219         /* Reset depth */
00220         history->depth = 0;
00221 
00222         /* Sanity check */
00223         entry = history_entry ( history, 0 );
00224         assert ( entry->string == NULL );
00225 }
00226 
00227 /**
00228  * Free history buffer
00229  *
00230  * @v history           History buffer
00231  */
00232 void history_free ( struct readline_history *history ) {
00233         struct readline_history_entry *entry;
00234         unsigned int i;
00235 
00236         /* Discard any temporary strings */
00237         for ( i = 0 ; i < ( sizeof ( history->entries ) /
00238                             sizeof ( history->entries[0] ) ) ; i++ ) {
00239                 entry = &history->entries[i];
00240                 assert ( entry->temp == NULL );
00241                 free ( entry->string );
00242         }
00243 }
00244 
00245 /**
00246  * Read line from console (with history)
00247  *
00248  * @v prompt            Prompt string
00249  * @v prefill           Prefill string, or NULL for no prefill
00250  * @v history           History buffer, or NULL for no history
00251  * @ret line            Line read from console (excluding terminating newline)
00252  * @ret rc              Return status code
00253  *
00254  * The returned line is allocated with malloc(); the caller must
00255  * eventually call free() to release the storage.
00256  */
00257 int readline_history ( const char *prompt, const char *prefill,
00258                        struct readline_history *history, char **line ) {
00259         char buf[READLINE_MAX];
00260         struct edit_string string;
00261         int key;
00262         int move_by;
00263         const char *new_string;
00264         int rc;
00265 
00266         /* Avoid returning uninitialised data on error */
00267         *line = NULL;
00268 
00269         /* Display prompt, if applicable */
00270         if ( prompt )
00271                 printf ( "%s", prompt );
00272 
00273         /* Ensure cursor is visible */
00274         printf ( "\033[?25h" );
00275 
00276         /* Initialise editable string */
00277         memset ( &string, 0, sizeof ( string ) );
00278         init_editstring ( &string, buf, sizeof ( buf ) );
00279         buf[0] = '\0';
00280 
00281         /* Prefill string, if applicable */
00282         if ( prefill ) {
00283                 replace_string ( &string, prefill );
00284                 sync_console ( &string );
00285         }
00286 
00287         while ( 1 ) {
00288                 /* Handle keypress */
00289                 key = edit_string ( &string, getkey ( 0 ) );
00290                 sync_console ( &string );
00291                 move_by = 0;
00292                 switch ( key ) {
00293                 case CR:
00294                 case LF:
00295                         *line = strdup ( buf );
00296                         rc = ( ( *line ) ? 0 : -ENOMEM );
00297                         goto done;
00298                 case CTRL_C:
00299                         rc = -ECANCELED;
00300                         goto done;
00301                 case KEY_UP:
00302                         move_by = 1;
00303                         break;
00304                 case KEY_DOWN:
00305                         move_by = -1;
00306                         break;
00307                 default:
00308                         /* Do nothing */
00309                         break;
00310                 }
00311 
00312                 /* Handle history movement, if applicable */
00313                 if ( move_by && history ) {
00314                         new_string = history_move ( history, move_by, buf );
00315                         if ( new_string ) {
00316                                 replace_string ( &string, new_string );
00317                                 sync_console ( &string );
00318                         }
00319                 }
00320         }
00321 
00322  done:
00323         putchar ( '\n' );
00324         if ( history ) {
00325                 if ( *line && (*line)[0] )
00326                         history_append ( history, *line );
00327                 history_cleanup ( history );
00328         }
00329         assert ( ( rc == 0 ) ^ ( *line == NULL ) );
00330         return rc;
00331 }
00332 
00333 /**
00334  * Read line from console
00335  *
00336  * @v prompt            Prompt string
00337  * @ret line            Line read from console (excluding terminating newline)
00338  *
00339  * The returned line is allocated with malloc(); the caller must
00340  * eventually call free() to release the storage.
00341  */
00342 char * readline ( const char *prompt ) {
00343         char *line;
00344 
00345         readline_history ( prompt, NULL, NULL, &line );
00346         return line;
00347 }