| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <ctype.h> | |
| #include <stdlib.h> | |
| #include <ipxe/keys.h> | |
| #include <ipxe/editstring.h> | |
| /** @file | |
| * | |
| * Editable strings | |
| * | |
| */ | |
| static __attribute__ (( nonnull ( 1 ) )) int | |
| insert_delete ( struct edit_string *string, size_t delete_len, | |
| const char *insert_text ); | |
| static __nonnull int insert_character ( struct edit_string *string, | |
| unsigned int character ); | |
| static __nonnull void delete_character ( struct edit_string *string ); | |
| static __nonnull void backspace ( struct edit_string *string ); | |
| static __nonnull void previous_word ( struct edit_string *string ); | |
| static __nonnull void kill_word ( struct edit_string *string ); | |
| static __nonnull void kill_sol ( struct edit_string *string ); | |
| static __nonnull void kill_eol ( struct edit_string *string ); | |
| /** | |
| * Insert and/or delete text within an editable string | |
| * | |
| * @v string Editable string | |
| * @v delete_len Length of text to delete from current cursor position | |
| * @v insert_text Text to insert at current cursor position, or NULL | |
| * @ret rc Return status code | |
| */ | |
| static int insert_delete ( struct edit_string *string, size_t delete_len, | |
| const char *insert_text ) { | |
| size_t old_len, max_delete_len, move_len, insert_len, new_len; | |
| char *buf; | |
| char *tmp; | |
| /* Prepare edit history */ | |
| string->mod_start = string->cursor; | |
| string->mod_end = string->cursor; | |
| /* Calculate lengths */ | |
| buf = *(string->buf); | |
| old_len = ( buf ? strlen ( buf ) : 0 ); | |
| assert ( string->cursor <= old_len ); | |
| max_delete_len = ( old_len - string->cursor ); | |
| if ( delete_len > max_delete_len ) | |
| delete_len = max_delete_len; | |
| move_len = ( max_delete_len - delete_len ); | |
| insert_len = ( insert_text ? strlen ( insert_text ) : 0 ); | |
| new_len = ( old_len - delete_len + insert_len ); | |
| /* Delete existing text */ | |
| memmove ( ( buf + string->cursor ), | |
| ( buf + string->cursor + delete_len ), move_len ); | |
| /* Reallocate string, ignoring failures if shrinking */ | |
| tmp = realloc ( buf, ( new_len + 1 /* NUL */ ) ); | |
| if ( tmp ) { | |
| buf = tmp; | |
| *(string->buf) = buf; | |
| } else if ( ( new_len > old_len ) || ( ! buf ) ) { | |
| return -ENOMEM; | |
| } | |
| /* Create space for inserted text */ | |
| memmove ( ( buf + string->cursor + insert_len ), | |
| ( buf + string->cursor ), move_len ); | |
| /* Copy inserted text to cursor position */ | |
| memcpy ( ( buf + string->cursor ), insert_text, insert_len ); | |
| string->cursor += insert_len; | |
| /* Terminate string */ | |
| buf[new_len] = '\0'; | |
| /* Update edit history */ | |
| string->mod_end = ( ( new_len > old_len ) ? new_len : old_len ); | |
| return 0; | |
| } | |
| /** | |
| * Insert character at current cursor position | |
| * | |
| * @v string Editable string | |
| * @v character Character to insert | |
| * @ret rc Return status code | |
| */ | |
| static int insert_character ( struct edit_string *string, | |
| unsigned int character ) { | |
| char insert_text[2] = { character, '\0' }; | |
| return insert_delete ( string, 0, insert_text ); | |
| } | |
| /** | |
| * Delete character at current cursor position | |
| * | |
| * @v string Editable string | |
| */ | |
| static void delete_character ( struct edit_string *string ) { | |
| int rc; | |
| rc = insert_delete ( string, 1, NULL ); | |
| assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); | |
| } | |
| /** | |
| * Delete character to left of current cursor position | |
| * | |
| * @v string Editable string | |
| */ | |
| static void backspace ( struct edit_string *string ) { | |
| if ( string->cursor > 0 ) { | |
| string->cursor--; | |
| delete_character ( string ); | |
| } | |
| } | |
| /** | |
| * Move to start of previous word | |
| * | |
| * @v string Editable string | |
| */ | |
| static void previous_word ( struct edit_string *string ) { | |
| const char *buf = *(string->buf); | |
| size_t cursor = string->cursor; | |
| while ( cursor && isspace ( buf[ cursor - 1 ] ) ) { | |
| cursor--; | |
| } | |
| while ( cursor && ( ! isspace ( buf[ cursor - 1 ] ) ) ) { | |
| cursor--; | |
| } | |
| string->cursor = cursor; | |
| } | |
| /** | |
| * Delete to end of previous word | |
| * | |
| * @v string Editable string | |
| */ | |
| static void kill_word ( struct edit_string *string ) { | |
| size_t old_cursor = string->cursor; | |
| int rc; | |
| previous_word ( string ); | |
| rc = insert_delete ( string, ( old_cursor - string->cursor ), NULL ); | |
| assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); | |
| } | |
| /** | |
| * Delete to start of line | |
| * | |
| * @v string Editable string | |
| */ | |
| static void kill_sol ( struct edit_string *string ) { | |
| size_t old_cursor = string->cursor; | |
| int rc; | |
| string->cursor = 0; | |
| rc = insert_delete ( string, old_cursor, NULL ); | |
| assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); | |
| } | |
| /** | |
| * Delete to end of line | |
| * | |
| * @v string Editable string | |
| */ | |
| static void kill_eol ( struct edit_string *string ) { | |
| int rc; | |
| rc = insert_delete ( string, ~( ( size_t ) 0 ), NULL ); | |
| assert ( ( rc == 0 ) || ( *(string->buf) == NULL ) ); | |
| } | |
| /** | |
| * Replace editable string | |
| * | |
| * @v string Editable string | |
| * @v replacement Replacement string, or NULL to empty the string | |
| * @ret rc Return status code | |
| * | |
| * Replace the entire content of the editable string and update the | |
| * edit history to allow the caller to bring the display into sync | |
| * with the string content. | |
| * | |
| * This function does not itself update the display in any way. | |
| * | |
| * Upon success, the string buffer is guaranteed to be non-NULL (even | |
| * if the replacement string is NULL or empty). | |
| * | |
| * Errors may safely be ignored if it is deemed that subsequently | |
| * failing to update the display will provide sufficient feedback to | |
| * the user. | |
| */ | |
| int replace_string ( struct edit_string *string, const char *replacement ) { | |
| string->cursor = 0; | |
| return insert_delete ( string, ~( ( size_t ) 0 ), replacement ); | |
| } | |
| /** | |
| * Edit editable string | |
| * | |
| * @v string Editable string | |
| * @v key Key pressed by user | |
| * @ret key Key returned to application, zero, or negative error | |
| * | |
| * Handles keypresses and updates the content of the editable string. | |
| * Basic line editing facilities (delete/insert/cursor) are supported. | |
| * If edit_string() understands and uses the keypress it will return | |
| * zero, otherwise it will return the original key. | |
| * | |
| * The string's edit history will be updated to allow the caller to | |
| * efficiently bring the display into sync with the string content. | |
| * | |
| * This function does not itself update the display in any way. | |
| * | |
| * Errors may safely be ignored if it is deemed that subsequently | |
| * failing to update the display will provide sufficient feedback to | |
| * the user. | |
| */ | |
| int edit_string ( struct edit_string *string, int key ) { | |
| const char *buf = *(string->buf); | |
| size_t len = ( buf ? strlen ( buf ) : 0 ); | |
| int retval = 0; | |
| /* Prepare edit history */ | |
| string->last_cursor = string->cursor; | |
| string->mod_start = string->cursor; | |
| string->mod_end = string->cursor; | |
| /* Interpret key */ | |
| if ( ( key >= 0x20 ) && ( key <= 0x7e ) ) { | |
| /* Printable character; insert at current position */ | |
| retval = insert_character ( string, key ); | |
| } else switch ( key ) { | |
| case KEY_BACKSPACE: | |
| /* Backspace */ | |
| backspace ( string ); | |
| break; | |
| case KEY_DC: | |
| case CTRL_D: | |
| /* Delete character */ | |
| delete_character ( string ); | |
| break; | |
| case CTRL_W: | |
| /* Delete word */ | |
| kill_word ( string ); | |
| break; | |
| case CTRL_U: | |
| /* Delete to start of line */ | |
| kill_sol ( string ); | |
| break; | |
| case CTRL_K: | |
| /* Delete to end of line */ | |
| kill_eol ( string ); | |
| break; | |
| case KEY_HOME: | |
| case CTRL_A: | |
| /* Start of line */ | |
| string->cursor = 0; | |
| break; | |
| case KEY_END: | |
| case CTRL_E: | |
| /* End of line */ | |
| string->cursor = len; | |
| break; | |
| case KEY_LEFT: | |
| case CTRL_B: | |
| /* Cursor left */ | |
| if ( string->cursor > 0 ) | |
| string->cursor--; | |
| break; | |
| case KEY_RIGHT: | |
| case CTRL_F: | |
| /* Cursor right */ | |
| if ( string->cursor < len ) | |
| string->cursor++; | |
| break; | |
| default: | |
| retval = key; | |
| break; | |
| } | |
| return retval; | |
| } |