From 0cdb070b76fc90b61fe435594e524fb8f9123fff Mon Sep 17 00:00:00 2001 From: Jacek Caban Date: Mon, 14 Sep 2020 18:46:34 +0200 Subject: [PATCH] conhost: Add support for ENABLE_LINE_INPUT. Imported from kernel32/readline.c. Signed-off-by: Jacek Caban Signed-off-by: Alexandre Julliard --- programs/conhost/conhost.c | 613 ++++++++++++++++++++++++++++++++++++- 1 file changed, 612 insertions(+), 1 deletion(-) diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index d93bcbfa60b..47856a943cb 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -507,6 +507,35 @@ static void read_from_buffer( struct console *console, size_t out_size ) free( console->read_buffer ); } +static void append_input_history( struct console *console, const WCHAR *str, size_t len ) +{ + struct history_line *ptr; + + if (!console->history_size) return; + + /* don't duplicate entry */ + if (console->history_mode && console->history_index && + console->history[console->history_index - 1]->len == len && + !memcmp( console->history[console->history_index - 1]->text, str, len )) + return; + + if (!(ptr = malloc( offsetof( struct history_line, text[len / sizeof(WCHAR)] )))) return; + ptr->len = len; + memcpy( ptr->text, str, len ); + + if (console->history_index < console->history_size) + { + console->history[console->history_index++] = ptr; + } + else + { + free( console->history[0]) ; + memmove( &console->history[0], &console->history[1], + (console->history_size - 1) * sizeof(*console->history) ); + console->history[console->history_size - 1] = ptr; + } +} + static void edit_line_update( struct console *console, unsigned int begin, unsigned int length ) { struct edit_line *ctx = &console->edit_line; @@ -535,6 +564,19 @@ static BOOL edit_line_grow( struct console *console, size_t length ) return TRUE; } +static void edit_line_delete( struct console *console, int begin, int end ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int len = end - begin; + + edit_line_update( console, begin, ctx->len - begin ); + if (end < ctx->len) + memmove( &ctx->buf[begin], &ctx->buf[end], (ctx->len - end) * sizeof(WCHAR)); + ctx->len -= len; + edit_line_update( console, 0, ctx->len ); + ctx->buf[ctx->len] = 0; +} + static void edit_line_insert( struct console *console, const WCHAR *str, unsigned int len ) { struct edit_line *ctx = &console->edit_line; @@ -566,6 +608,524 @@ static void edit_line_insert( struct console *console, const WCHAR *str, unsigne ctx->cursor += len; } +static void edit_line_save_yank( struct console *console, unsigned int begin, unsigned int end ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int len = end - begin; + if (len <= 0) return; + + free(ctx->yanked); + ctx->yanked = malloc( (len + 1) * sizeof(WCHAR) ); + if (!ctx->yanked) + { + ctx->status = STATUS_NO_MEMORY; + return; + } + memcpy( ctx->yanked, &ctx->buf[begin], len * sizeof(WCHAR) ); + ctx->yanked[len] = 0; +} + +static int edit_line_left_word_transition( struct console *console, int offset ) +{ + offset--; + while (offset >= 0 && !iswalnum( console->edit_line.buf[offset] )) offset--; + while (offset >= 0 && iswalnum( console->edit_line.buf[offset] )) offset--; + if (offset >= 0) offset++; + return max( offset, 0 ); +} + +static int edit_line_right_word_transition( struct console *console, int offset ) +{ + offset++; + while (offset <= console->edit_line.len && iswalnum( console->edit_line.buf[offset] )) + offset++; + while (offset <= console->edit_line.len && !iswalnum( console->edit_line.buf[offset] )) + offset++; + return min(offset, console->edit_line.len); +} + +static WCHAR *edit_line_history( struct console *console, unsigned int index ) +{ + WCHAR *ptr = NULL; + + if (index < console->history_index) + { + if ((ptr = malloc( console->history[index]->len + sizeof(WCHAR) ))) + { + memcpy( ptr, console->history[index]->text, console->history[index]->len ); + ptr[console->history[index]->len / sizeof(WCHAR)] = 0; + } + } + else if(console->edit_line.current_history) + { + if ((ptr = malloc( (lstrlenW(console->edit_line.current_history) + 1) * sizeof(WCHAR) ))) + lstrcpyW( ptr, console->edit_line.current_history ); + } + return ptr; +} + +static void edit_line_move_to_history( struct console *console, int index ) +{ + struct edit_line *ctx = &console->edit_line; + WCHAR *line = edit_line_history(console, index); + size_t len = line ? lstrlenW(line) : 0; + + /* save current line edition for recall when needed */ + if (ctx->history_index == console->history_index) + { + free( ctx->current_history ); + ctx->current_history = malloc( (ctx->len + 1) * sizeof(WCHAR) ); + if (ctx->current_history) + { + memcpy( ctx->current_history, ctx->buf, (ctx->len + 1) * sizeof(WCHAR) ); + } + else + { + ctx->status = STATUS_NO_MEMORY; + return; + } + } + + /* need to clean also the screen if new string is shorter than old one */ + edit_line_delete(console, 0, ctx->len); + ctx->cursor = 0; + /* insert new string */ + if (edit_line_grow(console, len + 1)) + { + edit_line_insert( console, line, len ); + ctx->history_index = index; + } + free(line); +} + +static void edit_line_find_in_history( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + int start_pos = ctx->history_index; + unsigned int len, oldoffset; + WCHAR *line; + + if (ctx->history_index && ctx->history_index == console->history_index + 1) + { + start_pos--; + ctx->history_index--; + } + + do + { + line = edit_line_history(console, ctx->history_index); + + if (ctx->history_index) ctx->history_index--; + else ctx->history_index = console->history_index; + + len = lstrlenW(line) + 1; + if (len >= ctx->cursor && !memcmp( ctx->buf, line, ctx->cursor * sizeof(WCHAR) )) + { + /* need to clean also the screen if new string is shorter than old one */ + edit_line_delete(console, 0, ctx->len); + + if (edit_line_grow(console, len)) + { + oldoffset = ctx->cursor; + ctx->cursor = 0; + edit_line_insert( console, line, len - 1 ); + ctx->cursor = oldoffset; + free(line); + return; + } + } + free(line); + } + while (ctx->history_index != start_pos); +} + +static void edit_line_move_left( struct console *console ) +{ + if (console->edit_line.cursor > 0) console->edit_line.cursor--; +} + +static void edit_line_move_right( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + if (ctx->cursor < ctx->len) ctx->cursor++; +} + +static void edit_line_move_left_word( struct console *console ) +{ + console->edit_line.cursor = edit_line_left_word_transition( console, console->edit_line.cursor ); +} + +static void edit_line_move_right_word( struct console *console ) +{ + console->edit_line.cursor = edit_line_right_word_transition( console, console->edit_line.cursor ); +} + +static void edit_line_move_home( struct console *console ) +{ + console->edit_line.cursor = 0; +} + +static void edit_line_move_end( struct console *console ) +{ + console->edit_line.cursor = console->edit_line.len; +} + +static void edit_line_set_mark( struct console *console ) +{ + console->edit_line.mark = console->edit_line.cursor; +} + +static void edit_line_exchange_mark( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int cursor; + + if (ctx->mark > ctx->len) return; + cursor = ctx->cursor; + ctx->cursor = ctx->mark; + ctx->mark = cursor; +} + +static void edit_line_copy_marked_zone( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int begin, end; + + if (ctx->mark > ctx->len || ctx->mark == ctx->cursor) return; + if (ctx->mark > ctx->cursor) + { + begin = ctx->cursor; + end = ctx->mark; + } + else + { + begin = ctx->mark; + end = ctx->cursor; + } + edit_line_save_yank( console, begin, end ); +} + +static void edit_line_transpose_char( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + WCHAR c; + + if (!ctx->cursor || ctx->cursor == ctx->len) return; + + c = ctx->buf[ctx->cursor]; + ctx->buf[ctx->cursor] = ctx->buf[ctx->cursor - 1]; + ctx->buf[ctx->cursor - 1] = c; + + edit_line_update( console, ctx->cursor - 1, 2 ); + ctx->cursor++; +} + +static void edit_line_transpose_words( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int left_offset = edit_line_left_word_transition( console, ctx->cursor ); + unsigned int right_offset = edit_line_right_word_transition( console, ctx->cursor ); + if (left_offset < ctx->cursor && right_offset > ctx->cursor) + { + unsigned int len_r = right_offset - ctx->cursor; + unsigned int len_l = ctx->cursor - left_offset; + char *tmp = malloc( len_r * sizeof(WCHAR) ); + if (!tmp) + { + ctx->status = STATUS_NO_MEMORY; + return; + } + + memcpy( tmp, &ctx->buf[ctx->cursor], len_r * sizeof(WCHAR) ); + memmove( &ctx->buf[left_offset + len_r], &ctx->buf[left_offset], + len_l * sizeof(WCHAR) ); + memcpy( &ctx->buf[left_offset], tmp, len_r * sizeof(WCHAR) ); + free(tmp); + + edit_line_update( console, left_offset, len_l + len_r ); + ctx->cursor = right_offset; + } +} + +static void edit_line_lower_case_word( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int new_offset = edit_line_right_word_transition( console, ctx->cursor ); + if (new_offset != ctx->cursor) + { + CharLowerBuffW( ctx->buf + ctx->cursor, new_offset - ctx->cursor + 1 ); + edit_line_update( console, ctx->cursor, new_offset - ctx->cursor + 1 ); + ctx->cursor = new_offset; + } +} + +static void edit_line_upper_case_word( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int new_offset = edit_line_right_word_transition( console, ctx->cursor ); + if (new_offset != ctx->cursor) + { + CharUpperBuffW( ctx->buf + ctx->cursor, new_offset - ctx->cursor + 1 ); + edit_line_update( console, ctx->cursor, new_offset - ctx->cursor + 1 ); + ctx->cursor = new_offset; + } +} + +static void edit_line_capitalize_word( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int new_offset = edit_line_right_word_transition( console, ctx->cursor ); + if (new_offset != ctx->cursor) + { + CharUpperBuffW( ctx->buf + ctx->cursor, 1 ); + CharLowerBuffW( ctx->buf + ctx->cursor + 1, new_offset - ctx->cursor ); + edit_line_update( console, ctx->cursor, new_offset - ctx->cursor + 1 ); + ctx->cursor = new_offset; + } +} + +static void edit_line_yank( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + if (ctx->yanked) edit_line_insert( console, ctx->yanked, wcslen(ctx->yanked) ); +} + +static void edit_line_kill_suffix( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + edit_line_save_yank( console, ctx->cursor, ctx->len ); + edit_line_delete( console, ctx->cursor, ctx->len ); +} + +static void edit_line_kill_prefix( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + if (ctx->cursor) + { + edit_line_save_yank( console, 0, ctx->cursor ); + edit_line_delete( console, 0, ctx->cursor ); + ctx->cursor = 0; + } +} + +static void edit_line_kill_marked_zone( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int begin, end; + + if (ctx->mark > ctx->len || ctx->mark == ctx->cursor) + return; + if (ctx->mark > ctx->cursor) + { + begin = ctx->cursor; + end = ctx->mark; + } + else + { + begin = ctx->mark; + end = ctx->cursor; + } + edit_line_save_yank( console, begin, end ); + edit_line_delete( console, begin, end ); + ctx->cursor = begin; +} + +static void edit_line_delete_prev( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + if (ctx->cursor) + { + edit_line_delete( console, ctx->cursor - 1, ctx->cursor ); + ctx->cursor--; + } +} + +static void edit_line_delete_char( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + if (ctx->cursor < ctx->len) + edit_line_delete( console, ctx->cursor, ctx->cursor + 1 ); +} + +static void edit_line_delete_left_word( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int new_offset = edit_line_left_word_transition( console, ctx->cursor ); + if (new_offset != ctx->cursor) + { + edit_line_delete( console, new_offset, ctx->cursor ); + ctx->cursor = new_offset; + } +} + +static void edit_line_delete_right_word( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + unsigned int new_offset = edit_line_right_word_transition( console, ctx->cursor ); + if (new_offset != ctx->cursor) + { + edit_line_delete( console, ctx->cursor, new_offset ); + } +} + +static void edit_line_move_to_prev_hist( struct console *console ) +{ + if (console->edit_line.history_index) + edit_line_move_to_history( console, console->edit_line.history_index - 1 ); +} + +static void edit_line_move_to_next_hist( struct console *console ) +{ + if (console->edit_line.history_index < console->history_index) + edit_line_move_to_history( console, console->edit_line.history_index + 1 ); +} + +static void edit_line_move_to_first_hist( struct console *console ) +{ + if (console->edit_line.history_index) + edit_line_move_to_history( console, 0 ); +} + +static void edit_line_move_to_last_hist( struct console *console ) +{ + if (console->edit_line.history_index != console->history_index) + edit_line_move_to_history( console, console->history_index ); +} + +static void edit_line_redraw( struct console *console ) +{ + if (console->mode & ENABLE_ECHO_INPUT) + edit_line_update( console, 0, console->edit_line.len ); +} + +static void edit_line_toggle_insert( struct console *console ) +{ + struct edit_line *ctx = &console->edit_line; + ctx->insert_key = !ctx->insert_key; + console->active->cursor_size = ctx->insert_key ? 100 : 25; +} + +static void edit_line_done( struct console *console ) +{ + console->edit_line.status = STATUS_SUCCESS; +} + +struct edit_line_key_entry +{ + WCHAR val; /* vk or unicode char */ + void (*func)( struct console *console ); +}; + +struct edit_line_key_map +{ + DWORD key_state; /* keyState (from INPUT_RECORD) to match */ + BOOL is_char; /* check vk or char */ + const struct edit_line_key_entry *entries; +}; + +#define CTRL(x) ((x) - '@') +static const struct edit_line_key_entry std_key_map[] = +{ + { VK_BACK, edit_line_delete_prev }, + { VK_RETURN, edit_line_done }, + { VK_DELETE, edit_line_delete_char }, + { 0 } +}; + +static const struct edit_line_key_entry emacs_key_map_ctrl[] = +{ + { CTRL('@'), edit_line_set_mark }, + { CTRL('A'), edit_line_move_home }, + { CTRL('B'), edit_line_move_left }, + { CTRL('D'), edit_line_delete_char }, + { CTRL('E'), edit_line_move_end }, + { CTRL('F'), edit_line_move_right }, + { CTRL('H'), edit_line_delete_prev }, + { CTRL('J'), edit_line_done }, + { CTRL('K'), edit_line_kill_suffix }, + { CTRL('L'), edit_line_redraw }, + { CTRL('M'), edit_line_done }, + { CTRL('N'), edit_line_move_to_next_hist }, + { CTRL('P'), edit_line_move_to_prev_hist }, + { CTRL('T'), edit_line_transpose_char }, + { CTRL('W'), edit_line_kill_marked_zone }, + { CTRL('X'), edit_line_exchange_mark }, + { CTRL('Y'), edit_line_yank }, + { 0 } +}; + +static const struct edit_line_key_entry emacs_key_map_alt[] = +{ + { 0x7f, edit_line_delete_left_word }, + { '<', edit_line_move_to_first_hist }, + { '>', edit_line_move_to_last_hist }, + { 'b', edit_line_move_left_word }, + { 'c', edit_line_capitalize_word }, + { 'd', edit_line_delete_right_word }, + { 'f', edit_line_move_right_word }, + { 'l', edit_line_lower_case_word }, + { 't', edit_line_transpose_words }, + { 'u', edit_line_upper_case_word }, + { 'w', edit_line_copy_marked_zone }, + { 0 } +}; + +static const struct edit_line_key_entry emacs_std_key_map[] = +{ + { VK_PRIOR, edit_line_move_to_prev_hist }, + { VK_NEXT, edit_line_move_to_next_hist }, + { VK_END, edit_line_move_end }, + { VK_HOME, edit_line_move_home }, + { VK_RIGHT, edit_line_move_right }, + { VK_LEFT, edit_line_move_left }, + { VK_INSERT, edit_line_toggle_insert }, + { 0 } +}; + +static const struct edit_line_key_map emacs_key_map[] = +{ + { 0, 0, std_key_map }, + { 0, 0, emacs_std_key_map }, + { RIGHT_ALT_PRESSED, 1, emacs_key_map_alt }, + { LEFT_ALT_PRESSED, 1, emacs_key_map_alt }, + { RIGHT_CTRL_PRESSED, 1, emacs_key_map_ctrl }, + { LEFT_CTRL_PRESSED, 1, emacs_key_map_ctrl }, + { 0 } +}; + +static const struct edit_line_key_entry win32_std_key_map[] = +{ + { VK_LEFT, edit_line_move_left }, + { VK_RIGHT, edit_line_move_right }, + { VK_HOME, edit_line_move_home }, + { VK_END, edit_line_move_end }, + { VK_UP, edit_line_move_to_prev_hist }, + { VK_DOWN, edit_line_move_to_next_hist }, + { VK_INSERT, edit_line_toggle_insert }, + { VK_F8, edit_line_find_in_history }, + { 0 } +}; + +static const struct edit_line_key_entry win32_key_map_ctrl[] = +{ + { VK_LEFT, edit_line_move_left_word }, + { VK_RIGHT, edit_line_move_right_word }, + { VK_END, edit_line_kill_suffix }, + { VK_HOME, edit_line_kill_prefix }, + { 0 } +}; + +static const struct edit_line_key_map win32_key_map[] = +{ + { 0, 0, std_key_map }, + { SHIFT_PRESSED, 0, std_key_map }, + { 0, 0, win32_std_key_map }, + { RIGHT_CTRL_PRESSED, 0, win32_key_map_ctrl }, + { LEFT_CTRL_PRESSED, 0, win32_key_map_ctrl }, + { 0 } +}; +#undef CTRL + static unsigned int edit_line_string_width( const WCHAR *str, unsigned int len) { unsigned int i, offset = 0; @@ -669,6 +1229,7 @@ static NTSTATUS process_console_input( struct console *console ) for (i = 0; i < console->record_count && ctx->status == STATUS_PENDING; i++) { + void (*func)( struct console *console ) = NULL; INPUT_RECORD ir = console->records[i]; if (ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown) continue; @@ -677,7 +1238,45 @@ static NTSTATUS process_console_input( struct console *console ) ir.Event.KeyEvent.wVirtualKeyCode, ir.Event.KeyEvent.wVirtualScanCode, ir.Event.KeyEvent.uChar.UnicodeChar, ir.Event.KeyEvent.dwControlKeyState ); - if (ir.Event.KeyEvent.uChar.UnicodeChar && !(ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED)) + if (console->mode & ENABLE_LINE_INPUT) + { + const struct edit_line_key_entry *entry; + const struct edit_line_key_map *map; + unsigned int state; + + /* mask out some bits which don't interest us */ + state = ir.Event.KeyEvent.dwControlKeyState & ~(NUMLOCK_ON|SCROLLLOCK_ON|CAPSLOCK_ON|ENHANCED_KEY); + + func = NULL; + for (map = console->edition_mode ? emacs_key_map : win32_key_map; map->entries != NULL; map++) + { + if (map->key_state != state) + continue; + if (map->is_char) + { + for (entry = &map->entries[0]; entry->func != 0; entry++) + if (entry->val == ir.Event.KeyEvent.uChar.UnicodeChar) break; + } + else + { + for (entry = &map->entries[0]; entry->func != 0; entry++) + if (entry->val == ir.Event.KeyEvent.wVirtualKeyCode) break; + + } + if (entry->func) + { + func = entry->func; + break; + } + } + } + + ctx->insert_mode = ((console->mode & (ENABLE_INSERT_MODE | ENABLE_EXTENDED_FLAGS)) == + (ENABLE_INSERT_MODE | ENABLE_EXTENDED_FLAGS)) + ^ ctx->insert_key; + + if (func) func( console ); + else if (ir.Event.KeyEvent.uChar.UnicodeChar && !(ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED)) edit_line_insert( console, &ir.Event.KeyEvent.uChar.UnicodeChar, 1 ); if (!(console->mode & ENABLE_LINE_INPUT) && ctx->status == STATUS_PENDING && @@ -695,6 +1294,18 @@ static NTSTATUS process_console_input( struct console *console ) if (console->mode & ENABLE_ECHO_INPUT) update_read_output( console ); if (ctx->status == STATUS_PENDING) return STATUS_SUCCESS; + if (!ctx->status && (console->mode & ENABLE_LINE_INPUT)) + { + if (ctx->len) append_input_history( console, ctx->buf, ctx->len * sizeof(WCHAR) ); + if (edit_line_grow(console, 2)) + { + ctx->buf[ctx->len++] = '\r'; + ctx->buf[ctx->len++] = '\n'; + ctx->buf[ctx->len] = 0; + TRACE( "return %s\n", debugstr_wn( ctx->buf, ctx->len )); + } + } + console->read_buffer = ctx->buf; console->read_buffer_count = ctx->len; console->read_buffer_size = ctx->size;