This serie implements: - filename completion in cmd - CONSOLE_READCONSOLE_CONTROL parameter in ReadConsoleW
v1 -> v2: + added tests in conhost for that (Jacek: thanks for warning, but it's a real pain ;-) + minor updates thanks to the tests
Signed-off-by: Eric Pouech eric.pouech@gmail.com ---
Eric Pouech (6): kernelbase/console, programs/conhost: support CONSOLE_READCONSOLE_CONTROL in ReadConsoleW dlls/kernelbase, programs/conhost: return key state in ReadConsoleW with control programs/conhost: handle csi escape sequence for shift tab programs/conhost/tests: added tests for shift-tab processing programs/conhost/tests: add tests for ReadConsoleW with control programs/cmd: implement filename completion
dlls/kernelbase/console.c | 36 ++++- include/wine/condrv.h | 1 + programs/cmd/batch.c | 252 ++++++++++++++++++++++++++++++++++- programs/conhost/conhost.c | 120 +++++++++++++---- programs/conhost/conhost.h | 2 + programs/conhost/tests/tty.c | 192 ++++++++++++++++++++++++-- server/console.c | 1 + 7 files changed, 564 insertions(+), 40 deletions(-)
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- dlls/kernelbase/console.c | 32 +++++++++++++- include/wine/condrv.h | 1 programs/conhost/conhost.c | 98 +++++++++++++++++++++++++++++++++++--------- programs/conhost/conhost.h | 1 server/console.c | 1 5 files changed, 112 insertions(+), 21 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index 1f9eda845a1..1f23292d467 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -2021,8 +2021,36 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun return FALSE; }
- ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer, - length * sizeof(WCHAR), count ); + if (reserved) + { + CONSOLE_READCONSOLE_CONTROL* crc = reserved; + char *tmp; + + if (crc->nLength != sizeof(*crc) || crc->nInitialChars >= length) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR) ))) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + return FALSE; + } + + memcpy( tmp, &crc->dwCtrlWakeupMask, sizeof(DWORD) ); + memcpy( tmp + sizeof(DWORD), buffer, crc->nInitialChars * sizeof(WCHAR) ); + ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE_CONTROL, + tmp, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR), + buffer, length * sizeof(WCHAR), + count ); + crc->dwConsoleKeyState = 0; + HeapFree( GetProcessHeap(), 0, tmp ); + } + else + { + ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE, NULL, 0, buffer, + length * sizeof(WCHAR), count ); + } if (ret) *count /= sizeof(WCHAR); return ret; } diff --git a/include/wine/condrv.h b/include/wine/condrv.h index 4515249a673..1000e62e765 100644 --- a/include/wine/condrv.h +++ b/include/wine/condrv.h @@ -44,6 +44,7 @@ #define IOCTL_CONDRV_FLUSH CTL_CODE(FILE_DEVICE_CONSOLE, 21, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_CONDRV_GET_WINDOW CTL_CODE(FILE_DEVICE_CONSOLE, 22, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_CONDRV_GET_PROCESS_LIST CTL_CODE(FILE_DEVICE_CONSOLE, 23, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_CONDRV_READ_CONSOLE_CONTROL CTL_CODE(FILE_DEVICE_CONSOLE, 24, METHOD_BUFFERED, FILE_READ_ACCESS)
/* console output ioctls */ #define IOCTL_CONDRV_WRITE_CONSOLE CTL_CODE(FILE_DEVICE_CONSOLE, 30, METHOD_BUFFERED, FILE_WRITE_ACCESS) diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index 8b03d749d21..1a993541b58 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -492,8 +492,9 @@ static void read_from_buffer( struct console *console, size_t out_size ) switch( console->read_ioctl ) { case IOCTL_CONDRV_READ_CONSOLE: + case IOCTL_CONDRV_READ_CONSOLE_CONTROL: out_size = min( out_size, console->read_buffer_count * sizeof(WCHAR) ); - read_complete( console, STATUS_SUCCESS, console->read_buffer, out_size, console->record_count != 0 ); + read_complete( console, STATUS_SUCCESS, console->read_buffer, out_size, console->record_count != 0 ); read_len = out_size / sizeof(WCHAR); break; case IOCTL_CONDRV_READ_FILE: @@ -1155,7 +1156,7 @@ static unsigned int edit_line_string_width( const WCHAR *str, unsigned int len) return offset; }
-static void update_read_output( struct console *console ) +static void update_read_output( struct console *console, BOOL newline ) { struct screen_buffer *screen_buffer = console->active; struct edit_line *ctx = &console->edit_line; @@ -1203,7 +1204,7 @@ static void update_read_output( struct console *console ) } }
- if (!ctx->status) + if (newline) { offset = edit_line_string_width( ctx->buf, ctx->len ); screen_buffer->cursor_x = 0; @@ -1241,10 +1242,14 @@ static void update_read_output( struct console *console ) update_window_config( screen_buffer->console, TRUE ); }
+/* can end on any ctrl-character: from 0x00 up to 0x1F) */ +#define FIRST_NON_CONTROL_CHAR (L' ') + static NTSTATUS process_console_input( struct console *console ) { struct edit_line *ctx = &console->edit_line; unsigned int i; + WCHAR ctrl_value = FIRST_NON_CONTROL_CHAR;
switch (console->read_ioctl) { @@ -1252,6 +1257,7 @@ static NTSTATUS process_console_input( struct console *console ) if (console->record_count) read_console_input( console, console->pending_read ); return STATUS_SUCCESS; case IOCTL_CONDRV_READ_CONSOLE: + case IOCTL_CONDRV_READ_CONSOLE_CONTROL: case IOCTL_CONDRV_READ_FILE: break; default: @@ -1284,6 +1290,19 @@ static NTSTATUS process_console_input( struct console *console ) /* mask out some bits which don't interest us */ state = ir.Event.KeyEvent.dwControlKeyState & ~(NUMLOCK_ON|SCROLLLOCK_ON|CAPSLOCK_ON|ENHANCED_KEY);
+ if (ctx->ctrl_mask && + ir.Event.KeyEvent.uChar.UnicodeChar && + ir.Event.KeyEvent.uChar.UnicodeChar < FIRST_NON_CONTROL_CHAR) + { + if (ctx->ctrl_mask & (1u << ir.Event.KeyEvent.uChar.UnicodeChar)) + { + ctrl_value = ir.Event.KeyEvent.uChar.UnicodeChar; + ctx->status = STATUS_SUCCESS; + TRACE("Found ctrl char in mask: ^%lc %x\n", ir.Event.KeyEvent.uChar.UnicodeChar + '@', ctx->ctrl_mask); + continue; + } + if (ir.Event.KeyEvent.uChar.UnicodeChar == 10) continue; + } func = NULL; for (map = console->edition_mode ? emacs_key_map : win32_key_map; map->entries != NULL; map++) { @@ -1326,7 +1345,7 @@ static NTSTATUS process_console_input( struct console *console ) } else if (ctx->len >= console->pending_read / sizeof(WCHAR)) ctx->status = STATUS_SUCCESS; - } + } }
if (console->record_count > i) memmove( console->records, console->records + i, @@ -1336,19 +1355,26 @@ static NTSTATUS process_console_input( struct console *console ) if (ctx->status == STATUS_PENDING && !(console->mode & ENABLE_LINE_INPUT) && ctx->len) ctx->status = STATUS_SUCCESS;
- if (console->mode & ENABLE_ECHO_INPUT) update_read_output( console ); + if (console->mode & ENABLE_ECHO_INPUT) update_read_output( console, !ctx->status && ctrl_value == FIRST_NON_CONTROL_CHAR ); 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)) + if (ctrl_value < FIRST_NON_CONTROL_CHAR) { - 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 )); + edit_line_insert( console, &ctrl_value, 1 ); } + else + { + 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; @@ -1365,8 +1391,10 @@ static NTSTATUS process_console_input( struct console *console ) return STATUS_SUCCESS; }
-static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_t out_size ) +static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_t out_size, + const WCHAR *initial, unsigned int initial_len, unsigned int ctrl_mask ) { + struct edit_line *ctx = &console->edit_line; TRACE("\n");
if (out_size > INT_MAX) @@ -1382,11 +1410,31 @@ static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_ return STATUS_SUCCESS; }
- console->edit_line.history_index = console->history_index; - console->edit_line.home_x = console->active->cursor_x; - console->edit_line.home_y = console->active->cursor_y; - console->edit_line.status = STATUS_PENDING; - if (edit_line_grow( console, 1 )) console->edit_line.buf[0] = 0; + ctx->history_index = console->history_index; + ctx->home_x = console->active->cursor_x; + ctx->home_y = console->active->cursor_y; + ctx->status = STATUS_PENDING; + if (initial_len && edit_line_grow( console, initial_len + 1 )) + { + unsigned offset = edit_line_string_width( initial, initial_len ); + if (offset > ctx->home_x) + { + int deltay; + offset -= ctx->home_x; + deltay = offset / console->active->width; + ctx->home_y = (deltay >= ctx->home_y) ? ctx->home_y - deltay : 0; + ctx->home_x = console->active->width - 1 - (offset % console->active->width); + } + else + ctx->home_x -= offset; + ctx->cursor = initial_len; + memcpy( ctx->buf, initial, initial_len * sizeof(WCHAR) ); + ctx->buf[initial_len] = 0; + ctx->len = initial_len; + ctx->end_offset = initial_len; + } + else if (edit_line_grow( console, 1 )) ctx->buf[0] = 0; + ctx->ctrl_mask = ctrl_mask;
console->pending_read = out_size; return process_console_input( console ); @@ -2496,13 +2544,25 @@ static NTSTATUS console_input_ioctl( struct console *console, unsigned int code, case IOCTL_CONDRV_READ_CONSOLE: if (in_size || *out_size % sizeof(WCHAR)) return STATUS_INVALID_PARAMETER; ensure_tty_input_thread( console ); - status = read_console( console, code, *out_size ); + status = read_console( console, code, *out_size, NULL, 0, 0 ); + *out_size = 0; + return status; + + case IOCTL_CONDRV_READ_CONSOLE_CONTROL: + if ((in_size < sizeof(DWORD)) || ((in_size - sizeof(DWORD)) % sizeof(WCHAR)) || + (*out_size % sizeof(WCHAR))) + return STATUS_INVALID_PARAMETER; + ensure_tty_input_thread( console ); + status = read_console( console, code, *out_size, + (const WCHAR*)((const char*)in_data + sizeof(DWORD)), + (in_size - sizeof(DWORD)) / sizeof(WCHAR), + *(DWORD*)in_data ); *out_size = 0; return status;
case IOCTL_CONDRV_READ_FILE: ensure_tty_input_thread( console ); - status = read_console( console, code, *out_size ); + status = read_console( console, code, *out_size, NULL, 0, 0 ); *out_size = 0; return status;
diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index 3cbd2ed2d80..e5f70aa3ca4 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -69,6 +69,7 @@ struct edit_line unsigned int end_offset; /* offset of the last written char */ unsigned int home_x; /* home position */ unsigned int home_y; + unsigned int ctrl_mask; /* mask for ctrl characters for completion */ };
struct console diff --git a/server/console.c b/server/console.c index 3fb7d8ab237..5f3f50d006f 100644 --- a/server/console.c +++ b/server/console.c @@ -952,6 +952,7 @@ static int is_blocking_read_ioctl( unsigned int code ) { case IOCTL_CONDRV_READ_INPUT: case IOCTL_CONDRV_READ_CONSOLE: + case IOCTL_CONDRV_READ_CONSOLE_CONTROL: case IOCTL_CONDRV_READ_FILE: return 1; default:
Hi Eric,
The series looks mostly good for me.
On 2/23/22 18:05, Eric Pouech wrote:
- if (initial_len && edit_line_grow( console, initial_len + 1 ))
- {
unsigned offset = edit_line_string_width( initial, initial_len );
if (offset > ctx->home_x)
{
int deltay;
offset -= ctx->home_x;
deltay = offset / console->active->width;
ctx->home_y = (deltay >= ctx->home_y) ? ctx->home_y - deltay : 0;
Isn't the logic reverted here?
ctx->home_x = console->active->width - 1 - (offset % console->active->width);
I wonder if we should use use 0 for home_x in case of deltay >= home_y.
Thanks,
Jacek
Le 25/02/2022 à 20:10, Jacek Caban a écrit :
Hi Eric,
The series looks mostly good for me.
On 2/23/22 18:05, Eric Pouech wrote:
+ if (initial_len && edit_line_grow( console, initial_len + 1 )) + { + unsigned offset = edit_line_string_width( initial, initial_len ); + if (offset > ctx->home_x) + { + int deltay; + offset -= ctx->home_x; + deltay = offset / console->active->width; + ctx->home_y = (deltay >= ctx->home_y) ? ctx->home_y - deltay : 0;
Isn't the logic reverted here?
yes
+ ctx->home_x = console->active->width - 1 - (offset % console->active->width);
I wonder if we should use use 0 for home_x in case of deltay >= home_y.
actually, this was more to protect the rest of the code which isn't prepared for negative coordinates
IIRC I tested this UC and native supports this
changing the code for handling such case is not immediate, but I thought that the ratio real value vs effort was near 0 and not worth for now going for it
so we could either add a FIXME (and leave the 0) or add a FIXME and return an error
A+
On 2/27/22 15:02, Eric Pouech wrote:
Le 25/02/2022 à 20:10, Jacek Caban a écrit :
Hi Eric,
The series looks mostly good for me.
On 2/23/22 18:05, Eric Pouech wrote:
+ if (initial_len && edit_line_grow( console, initial_len + 1 )) + { + unsigned offset = edit_line_string_width( initial, initial_len ); + if (offset > ctx->home_x) + { + int deltay; + offset -= ctx->home_x; + deltay = offset / console->active->width; + ctx->home_y = (deltay >= ctx->home_y) ? ctx->home_y - deltay : 0;
Isn't the logic reverted here?
yes
+ ctx->home_x = console->active->width - 1 - (offset % console->active->width);
I wonder if we should use use 0 for home_x in case of deltay >= home_y.
actually, this was more to protect the rest of the code which isn't prepared for negative coordinates
IIRC I tested this UC and native supports this
changing the code for handling such case is not immediate, but I thought that the ratio real value vs effort was near 0 and not worth for now going for it
so we could either add a FIXME (and leave the 0) or add a FIXME and return an error
I commented mostly based on the line above (which seems more important). If it's closer to what we will eventually want (if we support negative home_y someday), that's fine with me, as long as we don't crash.
Thanks,
Jacek
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- dlls/kernelbase/console.c | 12 ++++++++---- programs/conhost/conhost.c | 10 ++++++++-- programs/conhost/conhost.h | 1 + 3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/dlls/kernelbase/console.c b/dlls/kernelbase/console.c index 1f23292d467..b2bb6c53fd9 100644 --- a/dlls/kernelbase/console.c +++ b/dlls/kernelbase/console.c @@ -2031,7 +2031,7 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } - if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR) ))) + if (!(tmp = HeapAlloc( GetProcessHeap(), 0, sizeof(DWORD) + length * sizeof(WCHAR) ))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return FALSE; @@ -2041,9 +2041,13 @@ BOOL WINAPI ReadConsoleW( HANDLE handle, void *buffer, DWORD length, DWORD *coun memcpy( tmp + sizeof(DWORD), buffer, crc->nInitialChars * sizeof(WCHAR) ); ret = console_ioctl( handle, IOCTL_CONDRV_READ_CONSOLE_CONTROL, tmp, sizeof(DWORD) + crc->nInitialChars * sizeof(WCHAR), - buffer, length * sizeof(WCHAR), - count ); - crc->dwConsoleKeyState = 0; + tmp, sizeof(DWORD) + length * sizeof(WCHAR), count ); + if (ret) + { + memcpy( &crc->dwConsoleKeyState, tmp, sizeof(DWORD) ); + *count -= sizeof(DWORD); + memcpy( buffer, tmp + sizeof(DWORD), *count ); + } HeapFree( GetProcessHeap(), 0, tmp ); } else diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index 1a993541b58..c2e7218f636 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -457,6 +457,8 @@ static NTSTATUS read_complete( struct console *console, NTSTATUS status, const v req->signal = signal; req->read = 1; req->status = status; + if (console->read_ioctl == IOCTL_CONDRV_READ_CONSOLE_CONTROL) + wine_server_add_data( req, &console->key_state, sizeof(console->key_state) ); wine_server_add_data( req, buf, size ); status = wine_server_call( req ); } @@ -1250,6 +1252,7 @@ static NTSTATUS process_console_input( struct console *console ) struct edit_line *ctx = &console->edit_line; unsigned int i; WCHAR ctrl_value = FIRST_NON_CONTROL_CHAR; + unsigned int ctrl_keyvalue = 0;
switch (console->read_ioctl) { @@ -1297,6 +1300,7 @@ static NTSTATUS process_console_input( struct console *console ) if (ctx->ctrl_mask & (1u << ir.Event.KeyEvent.uChar.UnicodeChar)) { ctrl_value = ir.Event.KeyEvent.uChar.UnicodeChar; + ctrl_keyvalue = ir.Event.KeyEvent.dwControlKeyState; ctx->status = STATUS_SUCCESS; TRACE("Found ctrl char in mask: ^%lc %x\n", ir.Event.KeyEvent.uChar.UnicodeChar + '@', ctx->ctrl_mask); continue; @@ -1363,6 +1367,7 @@ static NTSTATUS process_console_input( struct console *console ) if (ctrl_value < FIRST_NON_CONTROL_CHAR) { edit_line_insert( console, &ctrl_value, 1 ); + console->key_state = ctrl_keyvalue; } else { @@ -1404,6 +1409,7 @@ static NTSTATUS read_console( struct console *console, unsigned int ioctl, size_ }
console->read_ioctl = ioctl; + console->key_state = 0; if (!out_size || console->read_buffer_count) { read_from_buffer( console, out_size ); @@ -2550,10 +2556,10 @@ static NTSTATUS console_input_ioctl( struct console *console, unsigned int code,
case IOCTL_CONDRV_READ_CONSOLE_CONTROL: if ((in_size < sizeof(DWORD)) || ((in_size - sizeof(DWORD)) % sizeof(WCHAR)) || - (*out_size % sizeof(WCHAR))) + (*out_size < sizeof(DWORD)) || ((*out_size - sizeof(DWORD)) % sizeof(WCHAR))) return STATUS_INVALID_PARAMETER; ensure_tty_input_thread( console ); - status = read_console( console, code, *out_size, + status = read_console( console, code, *out_size - sizeof(DWORD), (const WCHAR*)((const char*)in_data + sizeof(DWORD)), (in_size - sizeof(DWORD)) / sizeof(WCHAR), *(DWORD*)in_data ); diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index e5f70aa3ca4..35876689419 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -88,6 +88,7 @@ struct console unsigned int read_ioctl; /* current read ioctl */ size_t pending_read; /* size of pending read buffer */ struct edit_line edit_line; /* edit line context */ + unsigned int key_state; struct console_window *window; WCHAR *title; /* console title */ struct history_line **history; /* lines history */
On 2/23/22 18:05, Eric Pouech wrote:
diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index e5f70aa3ca4..35876689419 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -88,6 +88,7 @@ struct console unsigned int read_ioctl; /* current read ioctl */ size_t pending_read; /* size of pending read buffer */ struct edit_line edit_line; /* edit line context */
- unsigned int key_state; struct console_window *window; WCHAR *title; /* console title */ struct history_line **history; /* lines history */
I think it would fit better in edit_line struct.
Thanks,
Jacek
Le 25/02/2022 à 20:19, Jacek Caban a écrit :
On 2/23/22 18:05, Eric Pouech wrote:
diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index e5f70aa3ca4..35876689419 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -88,6 +88,7 @@ struct console unsigned int read_ioctl; /* current read ioctl */ size_t pending_read; /* size of pending read buffer */ struct edit_line edit_line; /* edit line context */ + unsigned int key_state; struct console_window *window; WCHAR *title; /* console title */ struct history_line **history; /* lines history */
I think it would fit better in edit_line struct.
as the edit struct is zeroed upon completion (and before returning the result), it would also require moving
- write of the key state inside the edit buffer in process_console_input() (insert 2 new characters for the DWORD)
- hence handle in process_console_input that the layout of the buffer differs depending on ioctl
I thought it was more readable to keep the edit logic as is, and and on top of it returning or not the keystate flag
but see no reason why it wouldn't be doable
I'll wait on your feed back on previous mail and this one to resend the lot
A+
On 2/27/22 15:23, Eric Pouech wrote:
Le 25/02/2022 à 20:19, Jacek Caban a écrit :
On 2/23/22 18:05, Eric Pouech wrote:
diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index e5f70aa3ca4..35876689419 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -88,6 +88,7 @@ struct console unsigned int read_ioctl; /* current read ioctl */ size_t pending_read; /* size of pending read buffer */ struct edit_line edit_line; /* edit line context */ + unsigned int key_state; struct console_window *window; WCHAR *title; /* console title */ struct history_line **history; /* lines history */
I think it would fit better in edit_line struct.
as the edit struct is zeroed upon completion (and before returning the result), it would also require moving
- write of the key state inside the edit buffer in
process_console_input() (insert 2 new characters for the DWORD)
- hence handle in process_console_input that the layout of the buffer
differs depending on ioctl
I thought it was more readable to keep the edit logic as is, and and on top of it returning or not the keystate flag
but see no reason why it wouldn't be doable
Agreed, it's probably not worth the complication. That's fine with me.
Thanks,
Jacek
Le 28/02/2022 à 16:50, Jacek Caban a écrit :
On 2/27/22 15:23, Eric Pouech wrote:
Le 25/02/2022 à 20:19, Jacek Caban a écrit :
On 2/23/22 18:05, Eric Pouech wrote:
diff --git a/programs/conhost/conhost.h b/programs/conhost/conhost.h index e5f70aa3ca4..35876689419 100644 --- a/programs/conhost/conhost.h +++ b/programs/conhost/conhost.h @@ -88,6 +88,7 @@ struct console unsigned int read_ioctl; /* current read ioctl */ size_t pending_read; /* size of pending read buffer */ struct edit_line edit_line; /* edit line context */ + unsigned int key_state; struct console_window *window; WCHAR *title; /* console title */ struct history_line **history; /* lines history */
I think it would fit better in edit_line struct.
as the edit struct is zeroed upon completion (and before returning the result), it would also require moving
- write of the key state inside the edit buffer in
process_console_input() (insert 2 new characters for the DWORD)
- hence handle in process_console_input that the layout of the buffer
differs depending on ioctl
I thought it was more readable to keep the edit logic as is, and and on top of it returning or not the keystate flag
but see no reason why it wouldn't be doable
Agreed, it's probably not worth the complication. That's fine with me.
Thanks,
Jacek
ok I'll resend with the fix on the delta_y inversion (likely tomorrow depending on what Alexandre commits or not tonight)
A+
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/conhost/conhost.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/programs/conhost/conhost.c b/programs/conhost/conhost.c index c2e7218f636..21401acd1e8 100644 --- a/programs/conhost/conhost.c +++ b/programs/conhost/conhost.c @@ -1561,8 +1561,11 @@ static void char_key_press( struct console *console, WCHAR ch, unsigned int ctrl key_press( console, ch, vk, ctrl ); }
-static unsigned int escape_char_to_vk( WCHAR ch ) +static unsigned int escape_char_to_vk( WCHAR ch, unsigned int *ctrl, WCHAR *outuch ) { + if (ctrl) *ctrl = 0; + if (outuch) *outuch = '\0'; + switch (ch) { case 'A': return VK_UP; @@ -1575,6 +1578,8 @@ static unsigned int escape_char_to_vk( WCHAR ch ) case 'Q': return VK_F2; case 'R': return VK_F3; case 'S': return VK_F4; + case 'Z': if (ctrl && outuch) {*ctrl = SHIFT_PRESSED; *outuch = L'\t'; return VK_TAB;} + return 0; default: return 0; } } @@ -1612,7 +1617,8 @@ static unsigned int convert_modifiers( unsigned int n )
static unsigned int process_csi_sequence( struct console *console, const WCHAR *buf, size_t size ) { - unsigned int n, count = 0, params[8], params_cnt = 0, vk; + unsigned int n, count = 0, params[8], params_cnt = 0, vk, ctrl; + WCHAR outuch;
for (;;) { @@ -1626,9 +1632,9 @@ static unsigned int process_csi_sequence( struct console *console, const WCHAR * if (++count == size) return 0; }
- if ((vk = escape_char_to_vk( buf[count] ))) + if ((vk = escape_char_to_vk( buf[count], &ctrl, &outuch ))) { - key_press( console, 0, vk, params_cnt >= 2 ? convert_modifiers( params[1] ) : 0 ); + key_press( console, outuch, vk, params_cnt >= 2 ? convert_modifiers( params[1] ) : ctrl ); return count + 1; }
@@ -1664,7 +1670,7 @@ static unsigned int process_input_escape( struct console *console, const WCHAR *
case 'O': if (++count == size) break; - vk = escape_char_to_vk( buf[1] ); + vk = escape_char_to_vk( buf[1], NULL, NULL ); if (vk) { key_press( console, 0, vk, 0 );
Signed-off-by: Jacek Caban jacek@codeweavers.com
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/conhost/tests/tty.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/programs/conhost/tests/tty.c b/programs/conhost/tests/tty.c index eab3c37f0d9..1857cdd4aad 100644 --- a/programs/conhost/tests/tty.c +++ b/programs/conhost/tests/tty.c @@ -1295,6 +1295,7 @@ static void test_tty_input(void) { "\x1b[D", 0, VK_LEFT, 0 }, { "\x1b[H", 0, VK_HOME, 0 }, { "\x1b[F", 0, VK_END, 0 }, + { "\x1b[Z", 0x9, VK_TAB, SHIFT_PRESSED }, { "\x1b[2~", 0, VK_INSERT, 0 }, { "\x1b[3~", 0, VK_DELETE, 0 }, { "\x1b[5~", 0, VK_PRIOR, 0 }, @@ -1304,8 +1305,8 @@ static void test_tty_input(void) { "\x1b[18~", 0, VK_F7, 0 }, { "\x1b[19~", 0, VK_F8, 0 }, { "\x1b[20~", 0, VK_F9, 0 }, - { "\x1b[21~", 0, VK_F10, 0 }, /* 0x10 */ + { "\x1b[21~", 0, VK_F10, 0 }, { "\x1b[23~", 0, VK_F11, 0 }, { "\x1b[24~", 0, VK_F12, 0 }, { "\x1bOP", 0, VK_F1, 0 }, @@ -1321,8 +1322,8 @@ static void test_tty_input(void) { "\x1b[1;7A", 0, VK_UP, LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED }, { "\x1b[1;8A", 0, VK_UP, SHIFT_PRESSED | LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED }, { "\x1b[1;9A", 0, VK_UP, 0 }, - { "\x1b[1;10A", 0, VK_UP, SHIFT_PRESSED }, /* 0x20 */ + { "\x1b[1;10A", 0, VK_UP, SHIFT_PRESSED }, { "\x1b[1;11A", 0, VK_UP, LEFT_ALT_PRESSED }, { "\x1b[1;12A", 0, VK_UP, SHIFT_PRESSED | LEFT_ALT_PRESSED }, { "\x1b[1;13A", 0, VK_UP, LEFT_CTRL_PRESSED }, @@ -1338,6 +1339,7 @@ static void test_tty_input(void) { "\x1b""1", '1', '1', LEFT_ALT_PRESSED }, { "\x1b""x", 'x', 'X', LEFT_ALT_PRESSED }, { "\x1b""[", '[', VK_OEM_4, LEFT_ALT_PRESSED }, + /* 0x30 */ { "\x7f", '\b', VK_BACK, 0 }, };
Signed-off-by: Jacek Caban jacek@codeweavers.com
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/conhost/tests/tty.c | 186 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 6 deletions(-)
diff --git a/programs/conhost/tests/tty.c b/programs/conhost/tests/tty.c index 1857cdd4aad..56d8ca7437f 100644 --- a/programs/conhost/tests/tty.c +++ b/programs/conhost/tests/tty.c @@ -153,6 +153,7 @@ enum req_type REQ_READ_CONSOLE, REQ_READ_CONSOLE_A, REQ_READ_CONSOLE_FILE, + REQ_READ_CONSOLE_CONTROL, REQ_SCROLL, REQ_SET_ACTIVE, REQ_SET_CURSOR, @@ -201,6 +202,12 @@ struct pseudoconsole_req DWORD count; COORD coord; } fill; + struct + { + size_t size; + DWORD mask; + WCHAR initial[1]; + } control; } u; };
@@ -415,6 +422,21 @@ static void child_read_console_file(HANDLE pipe, size_t size) ok(ret, "WriteFile failed: %lu\n", GetLastError()); }
+static void child_read_console_control(HANDLE pipe, size_t size, DWORD control, const WCHAR* recall) +{ + char tmp[4096]; + struct pseudoconsole_req *req = (void *)tmp; + DWORD count; + BOOL ret; + + req->type = REQ_READ_CONSOLE_CONTROL; + req->u.control.size = size; + req->u.control.mask = control; + wcscpy(req->u.control.initial, recall); + ret = WriteFile(pipe, req, sizeof(*req) + wcslen(recall) * sizeof(WCHAR), &count, NULL); + ok(ret, "WriteFile failed: %lu\n", GetLastError()); +} + #define child_expect_read_result(a,b) child_expect_read_result_(__LINE__,a,b) static void child_expect_read_result_(unsigned int line, HANDLE pipe, const WCHAR *expect) { @@ -431,6 +453,25 @@ static void child_expect_read_result_(unsigned int line, HANDLE pipe, const WCHA ok_(__FILE__,line)(!memcmp(expect, buf, count), "unexpected data %s\n", wine_dbgstr_w(buf)); }
+#define child_expect_read_control_result(a,b,c) child_expect_read_control_result_(__LINE__,a,b,c) +static void child_expect_read_control_result_(unsigned int line, HANDLE pipe, const WCHAR *expect, DWORD state) +{ + size_t exlen = wcslen(expect); + WCHAR buf[4096]; + WCHAR *ptr = (void *)((char *)buf + sizeof(DWORD)); + DWORD count; + BOOL ret; + + ret = ReadFile(pipe, buf, sizeof(buf), &count, NULL); + ok_(__FILE__,line)(ret, "ReadFile failed: %lu\n", GetLastError()); + ok_(__FILE__,line)(count == sizeof(DWORD) + exlen * sizeof(WCHAR), "got %lu, expected %Iu\n", + count, sizeof(DWORD) + exlen * sizeof(WCHAR)); + buf[count / sizeof(WCHAR)] = 0; + todo_wine_if(*(DWORD *)buf != state && *(DWORD *)buf == 0) + ok_(__FILE__,line)(*(DWORD *)buf == state, "keyboard state: got %lx, expected %lx\n", *(DWORD *)buf, state); + ok_(__FILE__,line)(!memcmp(expect, ptr, count - sizeof(DWORD)), "unexpected data %s %s\n", wine_dbgstr_w(ptr), wine_dbgstr_w(expect)); +} + #define child_expect_read_result_a(a,b) child_expect_read_result_a_(__LINE__,a,b) static void child_expect_read_result_a_(unsigned int line, HANDLE pipe, const char *expect) { @@ -493,8 +534,9 @@ static void expect_key_input_(unsigned int line, unsigned int ctx, WCHAR ch, uns ctx, record.Event.KeyEvent.bKeyDown); ok_(__FILE__,line)(record.Event.KeyEvent.wRepeatCount == 1, "%x: wRepeatCount = %x\n", ctx, record.Event.KeyEvent.wRepeatCount); - ok_(__FILE__,line)(record.Event.KeyEvent.uChar.UnicodeChar == ch, "%x: UnicodeChar = %x\n", - ctx, record.Event.KeyEvent.uChar.UnicodeChar); + ok_(__FILE__,line)(record.Event.KeyEvent.uChar.UnicodeChar == ch, "%x: UnicodeChar = %x,%lc\n", + ctx, record.Event.KeyEvent.uChar.UnicodeChar, + record.Event.KeyEvent.uChar.UnicodeChar); ok_(__FILE__,line)(record.Event.KeyEvent.wVirtualKeyCode == vk, "%x: wVirtualKeyCode = %x, expected %x\n", ctx, record.Event.KeyEvent.wVirtualKeyCode, vk); @@ -552,19 +594,25 @@ static void expect_char_key_(unsigned int line, WCHAR ch) expect_key_pressed_(line, ch, ch, vk, ctrl); }
-#define test_cursor_pos(a,b) _test_cursor_pos(__LINE__,a,b) -static void _test_cursor_pos(unsigned line, int expect_x, int expect_y) +static void fetch_child_sb_info(CONSOLE_SCREEN_BUFFER_INFO *info) { struct pseudoconsole_req req = { REQ_GET_SB_INFO }; - CONSOLE_SCREEN_BUFFER_INFO info; DWORD read; BOOL ret;
ret = WriteFile(child_pipe, &req, sizeof(req), &read, NULL); ok(ret, "WriteFile failed: %lu\n", GetLastError());
- ret = ReadFile(child_pipe, &info, sizeof(info), &read, NULL); + ret = ReadFile(child_pipe, info, sizeof(*info), &read, NULL); ok(ret, "ReadFile failed: %lu\n", GetLastError()); +} + +#define test_cursor_pos(a,b) _test_cursor_pos(__LINE__,a,b) +static void _test_cursor_pos(unsigned line, int expect_x, int expect_y) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + + fetch_child_sb_info(&info);
ok_(__FILE__,line)(info.dwCursorPosition.X == expect_x, "dwCursorPosition.X = %u, expected %u\n", info.dwCursorPosition.X, expect_x); @@ -1276,6 +1324,109 @@ static void test_read_console(void) expect_empty_output(); }
+static void test_read_console_control(void) +{ + CONSOLE_SCREEN_BUFFER_INFO info1, info2; + char ctrl; + char buf[16]; + WCHAR bufw[16]; + + child_set_input_mode(child_pipe, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | + ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT | ENABLE_INSERT_MODE | + ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_AUTO_POSITION); + + /* test simple behavior */ + for (ctrl = 0; ctrl < ' '; ctrl++) + { + /* don't play with fire */ + if (ctrl == 0 || ctrl == 3 || ctrl == '\n' || ctrl == 27) continue; + + /* Simulate initial characters + * Note: as ReadConsole with CONTROL structure will need to back up the cursor + * up to the length of the initial characters, all the following tests ensure that + * this backup doesn't imply backing up to the previous line. + * This is the "regular" behavior when using completion. + */ + child_string_request(REQ_WRITE_CONSOLE, L"\rabc"); + skip_sequence("\x1b[25l"); /* broken hide cursor */ + skip_sequence("\x1b[?25l"); /* hide cursor */ + skip_sequence("\x1b[H"); + skip_sequence("\r"); + expect_output_sequence("abc"); + skip_sequence("\x1b[?25h"); /* show cursor */ + + fetch_child_sb_info(&info1); + child_read_console_control(child_pipe, 100, 1ul << ctrl, L"abc"); + strcpy(buf, "def."); buf[3] = ctrl; + write_console_pipe(buf); + wcscpy(bufw, L"abcdef."); bufw[6] = (WCHAR)ctrl; + child_expect_read_control_result(child_pipe, bufw, + (ctrl == '\t' || ctrl == '\r') ? 0 + : ((ctrl == 30 || ctrl == 31) ? (LEFT_CTRL_PRESSED | SHIFT_PRESSED) + : LEFT_CTRL_PRESSED)); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("def"); + skip_sequence("\x1b[?25h"); /* show cursor */ + fetch_child_sb_info(&info2); + ok(info1.dwCursorPosition.X + 3 == info2.dwCursorPosition.X, + "Bad x-position: expected %u => %u but got => %u instead\n", + info1.dwCursorPosition.X, info1.dwCursorPosition.X + 3, info2.dwCursorPosition.X); + ok(info1.dwCursorPosition.Y == info2.dwCursorPosition.Y, "Cursor shouldn't have changed line\n"); + + } + + /* test two different control characters in input */ + fetch_child_sb_info(&info1); + child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc"); + write_console_pipe("d\x02""ef\x01"); + child_expect_read_control_result(child_pipe, L"abcd\x02""ef\x01", LEFT_CTRL_PRESSED); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("d^Bef"); + skip_sequence("\x1b[?25h"); /* show cursor */ + fetch_child_sb_info(&info2); + ok(info1.dwCursorPosition.X + 5 == info2.dwCursorPosition.X, + "Bad x-position: expected %u => %u but got => %u instead\n", + info1.dwCursorPosition.X, info1.dwCursorPosition.X + 5, info2.dwCursorPosition.X); + ok(info1.dwCursorPosition.Y == info2.dwCursorPosition.Y, "Cursor shouldn't have changed line\n"); + + /* test that ctrl character not in mask is handled as without ctrl mask */ + child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc"); + write_console_pipe("d\ref\x01"); + child_expect_read_control_result(child_pipe, L"abcd\r\n", 0); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("d\r\n"); + skip_sequence("\x1b[?25h"); /* show cursor */ + expect_empty_output(); + /* see note above... ditto */ + child_string_request(REQ_WRITE_CONSOLE, L"abc"); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("abc"); + skip_sequence("\x1b[?25h"); /* show cursor */ + + child_read_console_control(child_pipe, 100, 1ul << '\x01', L"abc"); + child_expect_read_control_result(child_pipe, L"abcef\x01", LEFT_CTRL_PRESSED); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("ef"); + skip_sequence("\x1b[?25h"); /* show cursor */ + + /* test when output buffer becomes full before control event */ + child_read_console_control(child_pipe, 4, 1ul << '\x01', L"abc"); + write_console_pipe("def\x01"); + child_expect_read_control_result(child_pipe, L"abcd", LEFT_CTRL_PRESSED); + skip_sequence("\x1b[?25l"); /* hide cursor */ + expect_output_sequence("def"); + skip_sequence("\x1b[?25h"); /* show cursor */ + expect_empty_output(); + child_read_console_control(child_pipe, 20, 1ul << '\x01', L"abc"); + child_expect_read_control_result(child_pipe, L"ef\x01", 0); + + /* TODO: add tests: + * - when back initial characters goes back to previous line + * - edition of initial characters can occur + */ + expect_empty_output(); +} + static void test_tty_input(void) { INPUT_RECORD ir; @@ -1420,6 +1571,7 @@ static void child_process(HANDLE pipe) while(ReadFile(pipe, buf, sizeof(buf), &size, NULL)) { const struct pseudoconsole_req *req = (void *)buf; + switch (req->type) { case REQ_CREATE_SCREEN_BUFFER: @@ -1481,6 +1633,27 @@ static void child_process(HANDLE pipe) ok(ret, "WriteFile failed: %lu\n", GetLastError()); break;
+ case REQ_READ_CONSOLE_CONTROL: + { + CONSOLE_READCONSOLE_CONTROL crc; + WCHAR result[1024]; + WCHAR *ptr = (void *)((char *)result + sizeof(DWORD)); + + count = req->u.control.size; + memset(result, 0xcc, sizeof(result)); + crc.nLength = sizeof(crc); + crc.dwCtrlWakeupMask = req->u.control.mask; + crc.nInitialChars = wcslen(req->u.control.initial); + crc.dwConsoleKeyState = 0xa5; + memcpy(ptr, req->u.control.initial, crc.nInitialChars * sizeof(WCHAR)); + ret = ReadConsoleW(input, ptr, count, &count, &crc); + ok(ret, "ReadConsoleW failed: %lu\n", GetLastError()); + *(DWORD *)result = crc.dwConsoleKeyState; + ret = WriteFile(pipe, result, sizeof(DWORD) + count * sizeof(WCHAR), NULL, NULL); + ok(ret, "WriteFile failed: %lu\n", GetLastError()); + } + break; + case REQ_SCROLL: ret = ScrollConsoleScreenBufferW(output, &req->u.scroll.rect, NULL, req->u.scroll.dst, &req->u.scroll.fill); ok(ret, "ScrollConsoleScreenBuffer failed: %lu\n", GetLastError()); @@ -1641,6 +1814,7 @@ static void test_pseudoconsole(void) if (!broken_version) { test_tty_output(); + test_read_console_control(); test_read_console(); test_tty_input(); }
Signed-off-by: Eric Pouech eric.pouech@gmail.com
--- programs/cmd/batch.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 6 deletions(-)
diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c index 9a262c5fec5..34385a12a0e 100644 --- a/programs/cmd/batch.c +++ b/programs/cmd/batch.c @@ -227,6 +227,158 @@ WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, BOOL raw, return WCMD_parameter_with_delims (s, n, start, raw, wholecmdline, L" \t,=;"); }
+struct completion_info +{ + WCHAR** subfiles; + DWORD index_search; + DWORD num_results; +}; + +static WCHAR* dup_quoted(const WCHAR* path, const WCHAR* in) +{ + const WCHAR* last = wcsrchr(path, L'/'); + size_t dirlen; + size_t len; + WCHAR* ret; + + if (last) + { + WCHAR* last1 = wcschr(last + 1, L'\'); + if (last1) last = last1; + } + else last = wcsrchr(path, L'\'); + dirlen = last ? (last - path) + 1 : 0; + len = wcslen(in); + ret = malloc((dirlen + len + 1) * sizeof(WCHAR)); + if (ret) + { + if (dirlen) + { + memcpy(ret, path, (dirlen - 1) * sizeof(WCHAR)); + ret[dirlen - 1] = *last; + } + memcpy(ret + dirlen, in, len * sizeof(WCHAR)); + ret[dirlen + len] = L'\0'; + if (wcspbrk(ret, L" \t"")) /* need quotes */ + { + WCHAR* new = realloc(ret, (1 + dirlen + len + 1 + 1) * sizeof(WCHAR)); + if (!new) + { + free(ret); + return NULL; + } + ret = new; + memmove(ret + 1, ret, (dirlen + len) * sizeof(WCHAR)); + ret[0] = ret[1 + dirlen + len] = L'"'; + ret[1 + dirlen + len + 1] = L'\0'; + } + } + return ret; +} + +static BOOL init_completion(struct completion_info* ci, const WCHAR* from, DWORD len, BOOL forward) +{ + WCHAR* ptr; + HANDLE hff; + WIN32_FIND_DATAW fd; + BOOL in_quotes = FALSE; + DWORD i, j = 0; + + ptr = malloc((len + 1 + 1) * sizeof(WCHAR)); + if (!ptr) return FALSE; + for (i = 0; i < len; i++) + { + switch (from[i]) + { + case L'"': + in_quotes = !in_quotes; + continue; + case L'\': + if (!in_quotes && i + 1 < len && wcschr(L" \t"\", from[i + 1]) != NULL) + ++i; + break; + case L' ': + case L'\t': + /* shouldn't happen, as 'from' should contain a single argument */ + if (!in_quotes) + { + free(ptr); + return FALSE; + } + break; + } + ptr[j++] = from[i]; + } + ptr[j] = L'*'; + ptr[j + 1] = L'\0'; + hff = FindFirstFileW(ptr, &fd); + ci->num_results = 0; + ci->subfiles = NULL; + if (hff != INVALID_HANDLE_VALUE) + { + do + { + WCHAR** new; + if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..")) + continue; + new = realloc(ci->subfiles, (ci->num_results + 1) * sizeof(*ci->subfiles)); + if (!new) + { + FindClose(hff); + free(ptr); + return FALSE; + } + ci->subfiles = new; + if (!(ci->subfiles[ci->num_results++] = dup_quoted(ptr, fd.cFileName))) + { + FindClose(hff); + free(ptr); + return FALSE; + } + } while (FindNextFileW(hff, &fd)); + FindClose(hff); + } + free(ptr); + if (!ci->num_results) return FALSE; + ci->index_search = forward ? 0 : ci->num_results - 1; + return TRUE; +} + +static void dispose_completion(struct completion_info* ci) +{ + if (ci->subfiles) + { + unsigned i; + for (i = 0; i < ci->num_results; i++) + free(ci->subfiles[i]); + free(ci->subfiles); + } +} + +static WCHAR* next_completion(struct completion_info* ci, const WCHAR* from, DWORD len, BOOL forward) +{ + if (!ci->subfiles || len != wcslen(ci->subfiles[ci->index_search]) || /* FIXME better check no wcslen, just check last char */ + memcmp(ci->subfiles[ci->index_search], from, len * sizeof(WCHAR))) + { + dispose_completion(ci); + if (!init_completion(ci, from, len, forward)) return NULL; + } + else + { + if (forward) + { + if (++ci->index_search >= ci->num_results) + ci->index_search = 0; + } + else + { + if (ci->index_search-- == 0) + ci->index_search = ci->num_results - 1; + } + } + return ci->subfiles[ci->index_search]; +} + /**************************************************************************** * WCMD_fgets * @@ -249,7 +401,7 @@ WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) /* We can't use the native f* functions because of the filename syntax differences between DOS and Unix. Also need to lose the LF (or CRLF) from the line. */
- if (!ReadConsoleW(h, buf, noChars, &charsRead, NULL)) { + if (!VerifyConsoleIoHandle(h)) { LARGE_INTEGER filepos; char *bufA; UINT cp; @@ -282,13 +434,101 @@ WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h) heap_free(bufA); } else { - if (!charsRead) return NULL; + CONSOLE_READCONSOLE_CONTROL crc; + CONSOLE_SCREEN_BUFFER_INFO sbi; + struct completion_info ci = {NULL, 0, 0}; + DWORD nwritten; + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); + + crc.nLength = sizeof(crc); + crc.nInitialChars = 0; + crc.dwCtrlWakeupMask = 1 << '\t'; + crc.dwConsoleKeyState = 0; + GetConsoleScreenBufferInfo(hout, &sbi); + for (;;) + { + WCHAR* ptr; + /* we need to 0-terminate the string anyway */ + if (!ReadConsoleW(h, buf, noChars - 1, &charsRead, &crc) || !charsRead) + { + dispose_completion(&ci); + return NULL; + }
- /* Find first EOL */ - for (i = 0; i < charsRead; i++) { - if (buf[i] == '\n' || buf[i] == '\r') - break; + ptr = wmemchr(buf, L'\t', charsRead); + if (ptr) + { + int n = 0; + WCHAR* start; + WCHAR* completion; + + /* find start of word containing ptr (if any) */ + buf[charsRead] = '\0'; + for (;;) + { + WCHAR* next; + WCHAR* pmt = WCMD_parameter(buf, n, &next, TRUE, FALSE); + if (!pmt[0] || next > ptr) + { + start = ptr; + break; + } + if (ptr <= next + wcslen(pmt)) + { + start = next; + break; + } + n++; + } + completion = next_completion(&ci, start, ptr - start, !(crc.dwConsoleKeyState & SHIFT_PRESSED)); + if (completion) + { + DWORD newlen; + if (start - buf + wcslen(completion) + 1 > noChars) + { + memcpy(start, completion, (start - buf + noChars - 1) * sizeof(WCHAR)); + buf[noChars - 1] = L'\0'; + newlen = noChars - 1; + } + else + { + wcscpy(start, completion); + newlen = wcslen(buf); + } + SetConsoleCursorPosition(hout, sbi.dwCursorPosition); + WriteConsoleW(hout, buf, newlen, &nwritten, NULL); + if (newlen < charsRead) + { + /* clear remaining stuff on input line (FIXME doesn't handle escaped characters) */ + WCHAR space = L' '; + DWORD i; + CONSOLE_SCREEN_BUFFER_INFO saved_sbi; + + GetConsoleScreenBufferInfo(hout, &saved_sbi); + for (i = newlen; i < charsRead; i++) + WriteConsoleW(hout, &space, 1, &nwritten, NULL); + SetConsoleCursorPosition(hout, saved_sbi.dwCursorPosition); + } + charsRead = newlen; + buf[charsRead] = L'\0'; + } + else + { + /* Beep() -- not loud enough, maybe user won't hear it */ + charsRead = ptr - buf; + } + crc.nInitialChars = charsRead; + continue; + } + + /* Find first EOL */ + for (i = 0; i < charsRead; i++) { + if (buf[i] == '\n' || buf[i] == '\r') + break; + } + if (i < charsRead) break; } + dispose_completion(&ci); }
/* Truncate at EOL (or end of buffer) */