From 7f22a27a10655dc40528f35034cbdf8c9543241c Mon Sep 17 00:00:00 2001 From: Ryan Prichard Date: Wed, 24 Feb 2016 02:14:19 -0600 Subject: [PATCH 01/16] win: integrate winpty (WIP) Handling of process exit is still broken. It detects the moment when the child process exits, then quickly stops polling for process output. It should continue polling for output until the agent has scraped all of the process' output. This problem is easy to notice by running a command like "dir && exit", but even typing "exit" can manifest the problem -- the "t" might not appear. winpty's Cygwin adapter handles shutdown by waiting for the agent to close the CONOUT pipe, which it does after it has scraped the child's last output. AFAIK, neovim doesn't do anything interesting when winpty closes the CONOUT pipe. --- CMakeLists.txt | 5 + cmake/FindWinpty.cmake | 10 ++ src/nvim/CMakeLists.txt | 7 ++ src/nvim/os/pty_process_win.c | 189 ++++++++++++++++++++++++++++++++++ src/nvim/os/pty_process_win.h | 25 +++-- 5 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 cmake/FindWinpty.cmake create mode 100644 src/nvim/os/pty_process_win.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 594f631ba0..17e14bcbd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -351,6 +351,11 @@ endif() find_package(LibVterm REQUIRED) include_directories(SYSTEM ${LIBVTERM_INCLUDE_DIRS}) +if(WIN32) + find_package(Winpty REQUIRED) + include_directories(SYSTEM ${WINPTY_INCLUDE_DIRS}) +endif() + option(CLANG_ASAN_UBSAN "Enable Clang address & undefined behavior sanitizer for nvim binary." OFF) option(CLANG_MSAN "Enable Clang memory sanitizer for nvim binary." OFF) option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF) diff --git a/cmake/FindWinpty.cmake b/cmake/FindWinpty.cmake new file mode 100644 index 0000000000..8feafc58a8 --- /dev/null +++ b/cmake/FindWinpty.cmake @@ -0,0 +1,10 @@ +include(LibFindMacros) + +find_path(WINPTY_INCLUDE_DIR winpty.h) +set(WINPTY_INCLUDE_DIRS ${WINPTY_INCLUDE_DIR}) + +find_library(WINPTY_LIBRARY winpty) +find_program(WINPTY_AGENT_EXE winpty-agent.exe) +set(WINPTY_LIBRARIES ${WINPTY_LIBRARY}) + +find_package_handle_standard_args(Winpty DEFAULT_MSG WINPTY_LIBRARY WINPTY_INCLUDE_DIR) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index c46c0bed6d..5f9d08cfa3 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -111,6 +111,9 @@ foreach(sfile ${NVIM_SOURCES}) if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$") list(APPEND to_remove ${sfile}) endif() + if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$") + list(APPEND to_remove ${sfile}) + endif() endforeach() list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) @@ -350,6 +353,10 @@ if(Iconv_LIBRARIES) list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES}) endif() +if(WIN32) + list(APPEND NVIM_LINK_LIBRARIES ${WINPTY_LIBRARIES}) +endif() + # Put these last on the link line, since multiple things may depend on them. list(APPEND NVIM_LINK_LIBRARIES ${LIBUV_LIBRARIES} diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c new file mode 100644 index 0000000000..e75c92e7fb --- /dev/null +++ b/src/nvim/os/pty_process_win.c @@ -0,0 +1,189 @@ +#include +#include +#include + +#include "nvim/memory.h" +#include "nvim/os/pty_process_win.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process_win.c.generated.h" +#endif + +static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) +{ + uv_async_t *finish_async = (uv_async_t *)context; + uv_async_send(finish_async); +} + +bool pty_process_spawn(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Process *proc = (Process *)ptyproc; + bool success = false; + winpty_error_ptr_t err = NULL; + winpty_config_t *cfg = NULL; + winpty_spawn_config_t *spawncfg = NULL; + winpty_t *wp = NULL; + char *in_name = NULL, *out_name = NULL; + HANDLE process_handle = NULL; + + assert(proc->in && proc->out && !proc->err); + + if (!(cfg = winpty_config_new( + WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err))) { + goto cleanup; + } + winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); + + if (!(wp = winpty_open(cfg, &err))) { + goto cleanup; + } + + in_name = utf16_to_utf8(winpty_conin_name(wp)); + out_name = utf16_to_utf8(winpty_conout_name(wp)); + uv_pipe_connect( + xmalloc(sizeof(uv_connect_t)), + &proc->in->uv.pipe, + in_name, + pty_process_connect_cb); + uv_pipe_connect( + xmalloc(sizeof(uv_connect_t)), + &proc->out->uv.pipe, + out_name, + pty_process_connect_cb); + + // XXX: Provide the correct ptyprocess parameters (at least, the cmdline... + // probably cwd too? what about environ?) + if (!(spawncfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + L"C:\\Windows\\System32\\cmd.exe", + L"C:\\Windows\\System32\\cmd.exe", + NULL, NULL, + &err))) { + goto cleanup; + } + if (!winpty_spawn(wp, spawncfg, &process_handle, NULL, NULL, &err)) { + goto cleanup; + } + + uv_async_init(&proc->loop->uv, &ptyproc->finish_async, pty_process_finish2); + if (!RegisterWaitForSingleObject(&ptyproc->finish_wait, process_handle, + pty_process_finish1, &ptyproc->finish_async, INFINITE, 0)) { + abort(); + } + + ptyproc->wp = wp; + ptyproc->process_handle = process_handle; + wp = NULL; + process_handle = NULL; + success = true; + +cleanup: + winpty_error_free(err); + winpty_config_free(cfg); + winpty_spawn_config_free(spawncfg); + winpty_free(wp); + xfree(in_name); + xfree(out_name); + if (process_handle != NULL) { + CloseHandle(process_handle); + } + return success; +} + +void pty_process_resize(PtyProcess *ptyproc, uint16_t width, + uint16_t height) + FUNC_ATTR_NONNULL_ALL +{ + if (ptyproc->wp != NULL) { + winpty_set_size(ptyproc->wp, width, height, NULL); + } +} + +void pty_process_close(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Process *proc = (Process *)ptyproc; + + ptyproc->is_closing = true; + pty_process_close_master(ptyproc); + + uv_handle_t *finish_async_handle = (uv_handle_t *)&ptyproc->finish_async; + if (ptyproc->finish_wait != NULL) { + // Use INVALID_HANDLE_VALUE to block until either the wait is cancelled + // or the callback has signalled the uv_async_t. + UnregisterWaitEx(ptyproc->finish_wait, INVALID_HANDLE_VALUE); + uv_close(finish_async_handle, pty_process_finish_closing); + } else { + pty_process_finish_closing(finish_async_handle); + } +} + +void pty_process_close_master(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + if (ptyproc->wp != NULL) { + winpty_free(ptyproc->wp); + ptyproc->wp = NULL; + } +} + +void pty_process_teardown(Loop *loop) + FUNC_ATTR_NONNULL_ALL +{ +} + +// Returns a string freeable with xfree. Never returns NULL (OOM is a fatal +// error). Windows appears to replace invalid UTF-16 code points (i.e. +// unpaired surrogates) using U+FFFD (the replacement character). +static char *utf16_to_utf8(LPCWSTR str) + FUNC_ATTR_NONNULL_ALL +{ + int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + assert(len >= 1); // Even L"" has a non-zero length due to NUL terminator. + char *ret = xmalloc(len); + int len2 = WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, len, NULL, NULL); + assert(len == len2); + return ret; +} + +static void pty_process_connect_cb(uv_connect_t *req, int status) +{ + assert(status == 0); + xfree(req); +} + +static void pty_process_finish2(uv_async_t *finish_async) +{ + PtyProcess *ptyproc = + (PtyProcess *)((char *)finish_async - offsetof(PtyProcess, finish_async)); + Process *proc = (Process *)ptyproc; + + if (!ptyproc->is_closing) { + // If pty_process_close has already been called, be consistent and never + // call the internal_exit callback. + + DWORD exit_code = 0; + GetExitCodeProcess(ptyproc->process_handle, &exit_code); + proc->status = exit_code; + + if (proc->internal_exit_cb) { + proc->internal_exit_cb(proc); + } + } +} + +static void pty_process_finish_closing(uv_handle_t *finish_async) +{ + PtyProcess *ptyproc = + (PtyProcess *)((char *)finish_async - offsetof(PtyProcess, finish_async)); + Process *proc = (Process *)ptyproc; + + if (ptyproc->process_handle != NULL) { + CloseHandle(ptyproc->process_handle); + ptyproc->process_handle = NULL; + } + if (proc->internal_close_cb) { + proc->internal_close_cb(proc); + } +} diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 8e2b37a1c1..87b2b6545d 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -1,21 +1,23 @@ #ifndef NVIM_OS_PTY_PROCESS_WIN_H #define NVIM_OS_PTY_PROCESS_WIN_H +#include + +#include + #include "nvim/event/libuv_process.h" typedef struct pty_process { Process process; char *term_name; uint16_t width, height; + winpty_t *wp; + uv_async_t finish_async; + HANDLE finish_wait; + HANDLE process_handle; + bool is_closing; } PtyProcess; -#define pty_process_spawn(job) libuv_process_spawn((LibuvProcess *)job) -#define pty_process_close(job) libuv_process_close((LibuvProcess *)job) -#define pty_process_close_master(job) libuv_process_close((LibuvProcess *)job) -#define pty_process_resize(job, width, height) ( \ - (void)job, (void)width, (void)height, 0) -#define pty_process_teardown(loop) ((void)loop, 0) - static inline PtyProcess pty_process_init(Loop *loop, void *data) { PtyProcess rv; @@ -23,7 +25,16 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.term_name = NULL; rv.width = 80; rv.height = 24; + rv.wp = NULL; + // XXX: Zero rv.finish_async somehow? + rv.finish_wait = NULL; + rv.process_handle = NULL; + rv.is_closing = false; return rv; } +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_process_win.h.generated.h" +#endif + #endif // NVIM_OS_PTY_PROCESS_WIN_H From a79785675564a4c1f02f9aa54249fc6000187c0d Mon Sep 17 00:00:00 2001 From: Rui Abreu Ferreira Date: Fri, 10 Feb 2017 00:04:27 +0000 Subject: [PATCH 02/16] win/install: winpty-agent.exe --- src/nvim/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 5f9d08cfa3..9bb3fbfcbe 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -422,6 +422,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tee.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty-agent.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/D3Dcompiler_47.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ From 4b1f21de75f9981007d80aca8355239e8615d6bd Mon Sep 17 00:00:00 2001 From: erw7 Date: Tue, 28 Mar 2017 18:07:58 +0900 Subject: [PATCH 03/16] win: support :terminal --- src/nvim/CMakeLists.txt | 1 + src/nvim/os/pty_process_win.c | 244 +++++++++++++----- src/nvim/os/pty_process_win.h | 13 +- test/functional/fixtures/tty-test.c | 44 ++-- test/functional/terminal/buffer_spec.lua | 6 +- test/functional/terminal/cursor_spec.lua | 2 - test/functional/terminal/ex_terminal_spec.lua | 11 +- test/functional/terminal/helpers.lua | 7 +- test/functional/terminal/highlight_spec.lua | 5 +- test/functional/terminal/mouse_spec.lua | 5 +- test/functional/terminal/scrollback_spec.lua | 64 ++++- test/functional/terminal/window_spec.lua | 2 - 12 files changed, 290 insertions(+), 114 deletions(-) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 9bb3fbfcbe..2e0a35d4ab 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -436,6 +436,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Network.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Svg.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/Qt5Widgets.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/platforms/qwindows.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms/ ) diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index e75c92e7fb..ea95c1bb09 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -2,30 +2,53 @@ #include #include +#include "nvim/vim.h" +#include "nvim/ascii.h" #include "nvim/memory.h" +#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 #include "nvim/os/pty_process_win.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/pty_process_win.c.generated.h" #endif -static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) +static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) + FUNC_ATTR_NONNULL_ALL { - uv_async_t *finish_async = (uv_async_t *)context; - uv_async_send(finish_async); + PtyProcess *ptyproc = + (PtyProcess *)((uv_handle_t *)wait_eof_timer->data); + Process *proc = (Process *)ptyproc; + + if (!uv_is_readable(proc->out->uvstream)) { + uv_timer_stop(&ptyproc->wait_eof_timer); + pty_process_finish2(ptyproc); + } } -bool pty_process_spawn(PtyProcess *ptyproc) +static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) + FUNC_ATTR_NONNULL_ALL +{ + PtyProcess *ptyproc = (PtyProcess *)context; + Process *proc = (Process *)ptyproc; + + uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); + ptyproc->wait_eof_timer.data = (void *)ptyproc; + uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); +} + +int pty_process_spawn(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { Process *proc = (Process *)ptyproc; - bool success = false; + int status = 0; winpty_error_ptr_t err = NULL; winpty_config_t *cfg = NULL; winpty_spawn_config_t *spawncfg = NULL; winpty_t *wp = NULL; char *in_name = NULL, *out_name = NULL; HANDLE process_handle = NULL; + uv_connect_t *in_req = NULL, *out_req = NULL; + wchar_t *cmdline = NULL, *cwd = NULL; assert(proc->in && proc->out && !proc->err); @@ -33,52 +56,70 @@ bool pty_process_spawn(PtyProcess *ptyproc) WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err))) { goto cleanup; } - winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); + winpty_config_set_initial_size( + cfg, + ptyproc->width, + ptyproc->height); if (!(wp = winpty_open(cfg, &err))) { goto cleanup; } - in_name = utf16_to_utf8(winpty_conin_name(wp)); - out_name = utf16_to_utf8(winpty_conout_name(wp)); + if ((status = utf16_to_utf8(winpty_conin_name(wp), &in_name))) { + goto cleanup; + } + if ((status = utf16_to_utf8(winpty_conout_name(wp), &out_name))) { + goto cleanup; + } + in_req = xmalloc(sizeof(uv_connect_t)); + out_req = xmalloc(sizeof(uv_connect_t)); uv_pipe_connect( - xmalloc(sizeof(uv_connect_t)), + in_req, &proc->in->uv.pipe, in_name, pty_process_connect_cb); uv_pipe_connect( - xmalloc(sizeof(uv_connect_t)), + out_req, &proc->out->uv.pipe, out_name, pty_process_connect_cb); - // XXX: Provide the correct ptyprocess parameters (at least, the cmdline... - // probably cwd too? what about environ?) + if (proc->cwd != NULL && (status = utf8_to_utf16(proc->cwd, &cwd))) { + goto cleanup; + } + if ((status = build_cmdline(proc->argv, &cmdline))) { + goto cleanup; + } if (!(spawncfg = winpty_spawn_config_new( WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, - L"C:\\Windows\\System32\\cmd.exe", - L"C:\\Windows\\System32\\cmd.exe", - NULL, NULL, - &err))) { + NULL, cmdline, cwd, NULL, &err))) { goto cleanup; } if (!winpty_spawn(wp, spawncfg, &process_handle, NULL, NULL, &err)) { goto cleanup; } + proc->pid = GetProcessId(process_handle); - uv_async_init(&proc->loop->uv, &ptyproc->finish_async, pty_process_finish2); - if (!RegisterWaitForSingleObject(&ptyproc->finish_wait, process_handle, - pty_process_finish1, &ptyproc->finish_async, INFINITE, 0)) { + if (!RegisterWaitForSingleObject( + &ptyproc->finish_wait, + process_handle, pty_process_finish1, ptyproc, + INFINITE, WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) { abort(); } + while (in_req->handle || out_req->handle) { + uv_run(&proc->loop->uv, UV_RUN_ONCE); + } + ptyproc->wp = wp; ptyproc->process_handle = process_handle; wp = NULL; process_handle = NULL; - success = true; cleanup: + if (err != NULL) { + status = (int)winpty_error_code(err); + } winpty_error_free(err); winpty_config_free(cfg); winpty_spawn_config_free(spawncfg); @@ -88,7 +129,11 @@ cleanup: if (process_handle != NULL) { CloseHandle(process_handle); } - return success; + xfree(in_req); + xfree(out_req); + xfree(cmdline); + xfree(cwd); + return status; } void pty_process_resize(PtyProcess *ptyproc, uint16_t width, @@ -105,17 +150,10 @@ void pty_process_close(PtyProcess *ptyproc) { Process *proc = (Process *)ptyproc; - ptyproc->is_closing = true; pty_process_close_master(ptyproc); - uv_handle_t *finish_async_handle = (uv_handle_t *)&ptyproc->finish_async; - if (ptyproc->finish_wait != NULL) { - // Use INVALID_HANDLE_VALUE to block until either the wait is cancelled - // or the callback has signalled the uv_async_t. - UnregisterWaitEx(ptyproc->finish_wait, INVALID_HANDLE_VALUE); - uv_close(finish_async_handle, pty_process_finish_closing); - } else { - pty_process_finish_closing(finish_async_handle); + if (proc->internal_close_cb) { + proc->internal_close_cb(proc); } } @@ -133,57 +171,123 @@ void pty_process_teardown(Loop *loop) { } -// Returns a string freeable with xfree. Never returns NULL (OOM is a fatal -// error). Windows appears to replace invalid UTF-16 code points (i.e. -// unpaired surrogates) using U+FFFD (the replacement character). -static char *utf16_to_utf8(LPCWSTR str) +static void pty_process_connect_cb(uv_connect_t *req, int status) FUNC_ATTR_NONNULL_ALL { - int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); - assert(len >= 1); // Even L"" has a non-zero length due to NUL terminator. - char *ret = xmalloc(len); - int len2 = WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, len, NULL, NULL); - assert(len == len2); + assert(status == 0); + req->handle = NULL; +} + +static void pty_process_finish2(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL +{ + Process *proc = (Process *)ptyproc; + + UnregisterWaitEx(ptyproc->finish_wait, NULL); + uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL); + + DWORD exit_code = 0; + GetExitCodeProcess(ptyproc->process_handle, &exit_code); + proc->status = (int)exit_code; + + CloseHandle(ptyproc->process_handle); + ptyproc->process_handle = NULL; + + proc->internal_exit_cb(proc); +} + +static int build_cmdline(char **argv, wchar_t **cmdline) + FUNC_ATTR_NONNULL_ALL +{ + char *args = NULL; + size_t args_len = 0, argc = 0; + int ret; + QUEUE q; + QUEUE_INIT(&q); + + while (*argv) { + arg_T *arg = xmalloc(sizeof(arg_T)); + arg->arg = (char *)xmalloc(strlen(*argv) * 2 + 3); + quote_cmd_arg(arg->arg, *argv); + args_len += strlen(arg->arg); + QUEUE_INIT(&arg->node); + QUEUE_INSERT_TAIL(&q, &arg->node); + argc++; + argv++; + } + args_len += argc; + args = xmalloc(args_len); + *args = NUL; + while (1) { + QUEUE *head = QUEUE_HEAD(&q); + QUEUE_REMOVE(head); + arg_T *arg = QUEUE_DATA(head, arg_T, node); + xstrlcat(args, arg->arg, args_len); + xfree(arg->arg); + xfree(arg); + if (QUEUE_EMPTY(&q)) { + break; + } else { + xstrlcat(args, " ", args_len); + } + } + ret = utf8_to_utf16(args, cmdline); + xfree(args); return ret; } -static void pty_process_connect_cb(uv_connect_t *req, int status) +// Emulate quote_cmd_arg of libuv and quotes command line arguments +static void quote_cmd_arg(char *target, const char *source) + FUNC_ATTR_NONNULL_ALL { - assert(status == 0); - xfree(req); -} + size_t len = strlen(source); + size_t i; + bool quote_hit = true; + char *start = target; + char tmp; -static void pty_process_finish2(uv_async_t *finish_async) -{ - PtyProcess *ptyproc = - (PtyProcess *)((char *)finish_async - offsetof(PtyProcess, finish_async)); - Process *proc = (Process *)ptyproc; + if (len == 0) { + *(target++) = '"'; + *(target++) = '"'; + *target = NUL; + return; + } - if (!ptyproc->is_closing) { - // If pty_process_close has already been called, be consistent and never - // call the internal_exit callback. + if (NULL == strpbrk(source, " \t\"")) { + strcpy(target, source); + return; + } - DWORD exit_code = 0; - GetExitCodeProcess(ptyproc->process_handle, &exit_code); - proc->status = exit_code; + if (NULL == strpbrk(source, "\"\\")) { + *(target++) = '"'; + strncpy(target, source, len); + target += len; + *(target++) = '"'; + *target = NUL; + return; + } - if (proc->internal_exit_cb) { - proc->internal_exit_cb(proc); + *(target++) = NUL; + *(target++) = '"'; + for (i = len; i > 0; --i) { + *(target++) = source[i - 1]; + + if (quote_hit && source[i - 1] == '\\') { + *(target++) = '\\'; + } else if (source[i - 1] == '"') { + quote_hit = true; + *(target++) = '\\'; + } else { + quote_hit = false; } } -} - -static void pty_process_finish_closing(uv_handle_t *finish_async) -{ - PtyProcess *ptyproc = - (PtyProcess *)((char *)finish_async - offsetof(PtyProcess, finish_async)); - Process *proc = (Process *)ptyproc; - - if (ptyproc->process_handle != NULL) { - CloseHandle(ptyproc->process_handle); - ptyproc->process_handle = NULL; - } - if (proc->internal_close_cb) { - proc->internal_close_cb(proc); + *target = '"'; + while (start < target) { + tmp = *start; + *start = *target; + *target = tmp; + start++; + target--; } + return; } diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 87b2b6545d..806857f130 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -5,19 +5,24 @@ #include -#include "nvim/event/libuv_process.h" +#include "nvim/event/process.h" +#include "nvim/lib/queue.h" typedef struct pty_process { Process process; char *term_name; uint16_t width, height; winpty_t *wp; - uv_async_t finish_async; HANDLE finish_wait; HANDLE process_handle; - bool is_closing; + uv_timer_t wait_eof_timer; } PtyProcess; +typedef struct arg_S { + char *arg; + QUEUE node; +} arg_T; + static inline PtyProcess pty_process_init(Loop *loop, void *data) { PtyProcess rv; @@ -26,10 +31,8 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.width = 80; rv.height = 24; rv.wp = NULL; - // XXX: Zero rv.finish_async somehow? rv.finish_wait = NULL; rv.process_handle = NULL; - rv.is_closing = false; return rv; } diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index 7ba21d652a..dd94d1a256 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -15,10 +15,12 @@ uv_tty_t tty; #include bool owns_tty(void) { - HWND consoleWnd = GetConsoleWindow(); - DWORD dwProcessId; - GetWindowThreadProcessId(consoleWnd, &dwProcessId); - return GetCurrentProcessId() == dwProcessId; + /* XXX: We need to make proper detect owns tty */ + /* HWND consoleWnd = GetConsoleWindow(); */ + /* DWORD dwProcessId; */ + /* GetWindowThreadProcessId(consoleWnd, &dwProcessId); */ + /* return GetCurrentProcessId() == dwProcessId; */ + return true; } #else #include @@ -54,16 +56,18 @@ static void sig_handler(int signum) return; } } +#else +static void sigwinch_cb(uv_signal_t *handle, int signum) +{ + int width, height; + uv_tty_t out; + uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); + uv_tty_get_winsize(&out, &width, &height); + fprintf(stderr, "rows: %d, cols: %d\n", height, width); + uv_close((uv_handle_t *)&out, NULL); +} #endif -// static void sigwinch_cb(uv_signal_t *handle, int signum) -// { -// int width, height; -// uv_tty_t *tty = handle->data; -// uv_tty_get_winsize(tty, &width, &height); -// fprintf(stderr, "rows: %d, cols: %d\n", height, width); -// } - static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { buf->len = BUF_SIZE; @@ -88,7 +92,7 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_loop_t write_loop; uv_loop_init(&write_loop); uv_tty_t out; - uv_tty_init(&write_loop, &out, 1, 0); + uv_tty_init(&write_loop, &out, fileno(stdout), 0); uv_write_t req; uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); @@ -149,7 +153,11 @@ int main(int argc, char **argv) uv_prepare_init(uv_default_loop(), &prepare); uv_prepare_start(&prepare, prepare_cb); // uv_tty_t tty; +#ifndef WIN32 uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1); +#else + uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1); +#endif uv_tty_set_mode(&tty, UV_TTY_MODE_RAW); tty.data = &interrupted; uv_read_start(STRUCT_CAST(uv_stream_t, &tty), alloc_cb, read_cb); @@ -160,15 +168,17 @@ int main(int argc, char **argv) sa.sa_handler = sig_handler; sigaction(SIGHUP, &sa, NULL); sigaction(SIGWINCH, &sa, NULL); - // uv_signal_t sigwinch_watcher; - // uv_signal_init(uv_default_loop(), &sigwinch_watcher); - // sigwinch_watcher.data = &tty; - // uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); +#else + uv_signal_t sigwinch_watcher; + uv_signal_init(uv_default_loop(), &sigwinch_watcher); + uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); #endif uv_run(uv_default_loop(), UV_RUN_DEFAULT); +#ifndef WIN32 // XXX: Without this the SIGHUP handler is skipped on some systems. sleep(100); +#endif return 0; } diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 48b8512bf0..22ab0a8c21 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -6,8 +6,6 @@ local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.s local eq, neq = helpers.eq, helpers.neq local write_file = helpers.write_file -if helpers.pending_win32(pending) then return end - describe('terminal buffer', function() local screen @@ -72,6 +70,7 @@ describe('terminal buffer', function() end) it('cannot be modified directly', function() + if helpers.pending_win32(pending) then return end feed('dd') screen:expect([[ tty ready | @@ -160,6 +159,7 @@ describe('terminal buffer', function() end) it('handles loss of focus gracefully', function() + if helpers.pending_win32(pending) then return end -- Change the statusline to avoid printing the file name, which varies. nvim('set_option', 'statusline', '==========') feed_command('set laststatus=0') @@ -205,7 +205,7 @@ describe('terminal buffer', function() end) describe('No heap-buffer-overflow when using', function() - + if helpers.pending_win32(pending) then return end local testfilename = 'Xtestfile-functional-terminal-buffers_spec' before_each(function() diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 84d0322f12..d49f1bfc23 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -7,8 +7,6 @@ local feed_command = helpers.feed_command local hide_cursor = thelpers.hide_cursor local show_cursor = thelpers.show_cursor -if helpers.pending_win32(pending) then return end - describe('terminal cursor', function() local screen diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index be0fd9f8ff..5eb6a5f18c 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -3,10 +3,10 @@ local Screen = require('test.functional.ui.screen') local clear, wait, nvim = helpers.clear, helpers.wait, helpers.nvim local nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq local feed_command, eval = helpers.feed_command, helpers.eval - -if helpers.pending_win32(pending) then return end +local iswin = helpers.iswin describe(':terminal', function() + if helpers.pending_win32(pending) then return end local screen before_each(function() @@ -174,10 +174,15 @@ describe(':terminal (with fake shell)', function() eq('term://', string.match(eval('bufname("%")'), "^term://")) helpers.feed([[]]) feed_command([[find */shadacat.py]]) - eq('scripts/shadacat.py', eval('bufname("%")')) + if iswin() then + eq('scripts\\shadacat.py', eval('bufname("%")')) + else + eq('scripts/shadacat.py', eval('bufname("%")')) + end end) it('works with gf', function() + if helpers.pending_win32(pending) then return end terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) wait() screen:expect([[ diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index 3b04d17705..29381ab4f0 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -30,10 +30,14 @@ local function clear_attrs() feed_termcode('[0;10m') end -- mouse local function enable_mouse() feed_termcode('[?1002h') end local function disable_mouse() feed_termcode('[?1002l') end +local function wait_sigwinch() + helpers.sleep(1000) + hide_cursor() + show_cursor() +end local default_command = '["'..nvim_dir..'/tty-test'..'"]' - local function screen_setup(extra_rows, command, cols) extra_rows = extra_rows and extra_rows or 0 command = command and command or default_command @@ -112,5 +116,6 @@ return { clear_attrs = clear_attrs, enable_mouse = enable_mouse, disable_mouse = disable_mouse, + wait_sigwinch = wait_sigwinch, screen_setup = screen_setup } diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index bb40770235..fddc0bbb71 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -5,8 +5,6 @@ local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim local nvim_dir, command = helpers.nvim_dir, helpers.command local eq, eval = helpers.eq, helpers.eval -if helpers.pending_win32(pending) then return end - describe('terminal window highlighting', function() local screen @@ -55,6 +53,7 @@ describe('terminal window highlighting', function() end) local function pass_attrs() + if helpers.pending_win32(pending) then return end screen:expect(sub([[ tty ready | {NUM:text}text{10: } | @@ -69,6 +68,7 @@ describe('terminal window highlighting', function() it('will pass the corresponding attributes', pass_attrs) it('will pass the corresponding attributes on scrollback', function() + if helpers.pending_win32(pending) then return end pass_attrs() local lines = {} for i = 1, 8 do @@ -145,6 +145,7 @@ describe('terminal window highlighting with custom palette', function() end) it('will use the custom color', function() + if helpers.pending_win32(pending) then return end thelpers.set_fg(3) thelpers.feed_data('text') thelpers.clear_attrs() diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 9239c2ad31..29c62d7be7 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -4,8 +4,6 @@ local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval local feed, nvim = helpers.feed, helpers.nvim local feed_data = thelpers.feed_data -if helpers.pending_win32(pending) then return end - describe('terminal mouse', function() local screen @@ -67,6 +65,7 @@ describe('terminal mouse', function() end) it('will forward mouse clicks to the program', function() + if helpers.pending_win32(pending) then return end feed('<1,2>') screen:expect([[ line27 | @@ -80,6 +79,7 @@ describe('terminal mouse', function() end) it('will forward mouse scroll to the program', function() + if helpers.pending_win32(pending) then return end feed('<0,0>') screen:expect([[ line27 | @@ -94,6 +94,7 @@ describe('terminal mouse', function() end) describe('with a split window and other buffer', function() + if helpers.pending_win32(pending) then return end before_each(function() feed(':vsp') screen:expect([[ diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 05f81295c2..649bb4373b 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.feed_command +local iswin, wait_sigwinch = helpers.iswin, thelpers.wait_sigwinch local eval = helpers.eval local command = helpers.command local wait = helpers.wait @@ -11,8 +12,6 @@ local curbufmeths = helpers.curbufmeths local nvim = helpers.nvim local feed_data = thelpers.feed_data -if helpers.pending_win32(pending) then return end - describe('terminal scrollback', function() local screen @@ -142,6 +141,9 @@ describe('terminal scrollback', function() describe('and the height is decreased by 1', function() local function will_hide_top_line() screen:try_resize(screen._width, screen._height - 1) + if iswin() then + wait_sigwinch() + end screen:expect([[ line2 | line3 | @@ -158,6 +160,9 @@ describe('terminal scrollback', function() before_each(function() will_hide_top_line() screen:try_resize(screen._width, screen._height - 2) + if iswin() then + wait_sigwinch() + end end) it('will hide the top 3 lines', function() @@ -184,9 +189,13 @@ describe('terminal scrollback', function() describe('and the height is decreased by 2', function() before_each(function() screen:try_resize(screen._width, screen._height - 2) + if iswin() then + wait_sigwinch() + end end) local function will_delete_last_two_lines() + if helpers.pending_win32(pending) then return end screen:expect([[ tty ready | rows: 4, cols: 30 | @@ -200,9 +209,13 @@ describe('terminal scrollback', function() it('will delete the last two empty lines', will_delete_last_two_lines) describe('and then decreased by 1', function() + if helpers.pending_win32(pending) then return end before_each(function() will_delete_last_two_lines() screen:try_resize(screen._width, screen._height - 1) + if iswin() then + wait_sigwinch() + end end) it('will delete the last line and hide the first', function() @@ -245,6 +258,9 @@ describe('terminal scrollback', function() {3:-- TERMINAL --} | ]]) screen:try_resize(screen._width, screen._height - 3) + if iswin() then + wait_sigwinch() + end screen:expect([[ line4 | rows: 3, cols: 30 | @@ -257,6 +273,9 @@ describe('terminal scrollback', function() describe('and the height is increased by 1', function() local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) + if iswin() then + wait_sigwinch() + end screen:expect([[ line4 | rows: 3, cols: 30 | @@ -273,6 +292,9 @@ describe('terminal scrollback', function() pop_then_push() eq(8, curbuf('line_count')) screen:try_resize(screen._width, screen._height + 3) + if iswin() then + wait_sigwinch() + end end) local function pop3_then_push1() @@ -303,10 +325,14 @@ describe('terminal scrollback', function() it('will pop 3 lines and then push one back', pop3_then_push1) describe('and then by 4', function() + if helpers.pending_win32(pending) then return end before_each(function() pop3_then_push1() feed('Gi') screen:try_resize(screen._width, screen._height + 4) + if iswin() then + wait_sigwinch() + end end) it('will show all lines and leave a blank one at the end', function() @@ -384,10 +410,20 @@ describe("'scrollback' option", function() end it('set to 0 behaves as 1', function() - local screen = thelpers.screen_setup(nil, "['sh']", 30) + local screen + if iswin() then + screen = thelpers.screen_setup(nil, + "['powershell.exe', '-NoLogo', '-NoProfile', '-NoExit', '-Command', 'function global:prompt {return "..'"$"'.."}']", 30) + else + screen = thelpers.screen_setup(nil, "['sh']", 30) + end curbufmeths.set_option('scrollback', 0) - feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + if iswin() then + feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r') + else + feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + end screen:expect('line30 ', nil, nil, nil, true) retry(nil, nil, function() expect_lines(7) end) @@ -395,7 +431,13 @@ describe("'scrollback' option", function() end) it('deletes lines (only) if necessary', function() - local screen = thelpers.screen_setup(nil, "['sh']", 30) + local screen + if iswin() then + screen = thelpers.screen_setup(nil, + "['powershell.exe', '-NoLogo', '-NoProfile', '-NoExit', '-Command', 'function global:prompt {return "..'"$"'.."}']", 30) + else + screen = thelpers.screen_setup(nil, "['sh']", 30) + end curbufmeths.set_option('scrollback', 200) @@ -403,7 +445,11 @@ describe("'scrollback' option", function() screen:expect('$', nil, nil, nil, true) wait() - feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + if iswin() then + feed_data('for($i=1;$i -le 30;$i++){Write-Host \"line$i\"}\r') + else + feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') + end screen:expect('line30 ', nil, nil, nil, true) @@ -416,7 +462,11 @@ describe("'scrollback' option", function() -- Terminal job data is received asynchronously, may happen before the -- 'scrollback' option is synchronized with the internal sb_buffer. command('sleep 100m') - feed_data('for i in $(seq 1 40); do echo "line$i"; done\n') + if iswin() then + feed_data('for($i=1;$i -le 40;$i++){Write-Host \"line$i\"}\r') + else + feed_data('for i in $(seq 1 40); do echo "line$i"; done\n') + end screen:expect('line40 ', nil, nil, nil, true) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 888b1e1328..0f705cfe40 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -3,8 +3,6 @@ local thelpers = require('test.functional.terminal.helpers') local feed, clear = helpers.feed, helpers.clear local wait = helpers.wait -if helpers.pending_win32(pending) then return end - describe('terminal window', function() local screen From e635754e8e9a92d1537ccc980c63eaa2e9d732bb Mon Sep 17 00:00:00 2001 From: erw7 Date: Wed, 29 Mar 2017 21:39:58 +0900 Subject: [PATCH 04/16] win/pty: jobstart, jobstop: fix null-pointer dereference - Make sure that proc->in is not NULL, because nvim crashed when starting a job with pty. - Make sure that proc->out is not NULL, because nvim crashed when stopping a job opened with pty. --- src/nvim/os/pty_process_win.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index ea95c1bb09..101f468005 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -19,7 +19,7 @@ static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) (PtyProcess *)((uv_handle_t *)wait_eof_timer->data); Process *proc = (Process *)ptyproc; - if (!uv_is_readable(proc->out->uvstream)) { + if (!proc->out || !uv_is_readable(proc->out->uvstream)) { uv_timer_stop(&ptyproc->wait_eof_timer); pty_process_finish2(ptyproc); } @@ -50,7 +50,7 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_connect_t *in_req = NULL, *out_req = NULL; wchar_t *cmdline = NULL, *cwd = NULL; - assert(proc->in && proc->out && !proc->err); + assert(!proc->err); if (!(cfg = winpty_config_new( WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err))) { @@ -71,18 +71,22 @@ int pty_process_spawn(PtyProcess *ptyproc) if ((status = utf16_to_utf8(winpty_conout_name(wp), &out_name))) { goto cleanup; } - in_req = xmalloc(sizeof(uv_connect_t)); - out_req = xmalloc(sizeof(uv_connect_t)); - uv_pipe_connect( - in_req, - &proc->in->uv.pipe, - in_name, - pty_process_connect_cb); - uv_pipe_connect( - out_req, - &proc->out->uv.pipe, - out_name, - pty_process_connect_cb); + if (proc->in) { + in_req = xmalloc(sizeof(uv_connect_t)); + uv_pipe_connect( + in_req, + &proc->in->uv.pipe, + in_name, + pty_process_connect_cb); + } + if (proc->out) { + out_req = xmalloc(sizeof(uv_connect_t)); + uv_pipe_connect( + out_req, + &proc->out->uv.pipe, + out_name, + pty_process_connect_cb); + } if (proc->cwd != NULL && (status = utf8_to_utf16(proc->cwd, &cwd))) { goto cleanup; @@ -107,7 +111,7 @@ int pty_process_spawn(PtyProcess *ptyproc) abort(); } - while (in_req->handle || out_req->handle) { + while ((in_req && in_req->handle) || (out_req && out_req->handle)) { uv_run(&proc->loop->uv, UV_RUN_ONCE); } From 3b992f16889b45215ab6f867edaec5201776d579 Mon Sep 17 00:00:00 2001 From: erw7 Date: Thu, 30 Mar 2017 18:30:40 +0900 Subject: [PATCH 05/16] win/pty: quote_cmd_arg(): check bounds --- src/nvim/os/pty_process_win.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 101f468005..f3efe87d78 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -210,9 +210,10 @@ static int build_cmdline(char **argv, wchar_t **cmdline) QUEUE_INIT(&q); while (*argv) { + size_t buf_len = strlen(*argv) * 2 + 3; arg_T *arg = xmalloc(sizeof(arg_T)); - arg->arg = (char *)xmalloc(strlen(*argv) * 2 + 3); - quote_cmd_arg(arg->arg, *argv); + arg->arg = (char *)xmalloc(buf_len); + quote_cmd_arg(arg->arg, buf_len, *argv); args_len += strlen(arg->arg); QUEUE_INIT(&arg->node); QUEUE_INSERT_TAIL(&q, &arg->node); @@ -241,50 +242,50 @@ static int build_cmdline(char **argv, wchar_t **cmdline) } // Emulate quote_cmd_arg of libuv and quotes command line arguments -static void quote_cmd_arg(char *target, const char *source) +static void quote_cmd_arg(char *target, size_t remain, const char *source) FUNC_ATTR_NONNULL_ALL { - size_t len = strlen(source); + size_t src_len = strlen(source); size_t i; bool quote_hit = true; char *start = target; char tmp; - if (len == 0) { - *(target++) = '"'; - *(target++) = '"'; - *target = NUL; + if (src_len == 0) { + snprintf(target, remain, "\"\""); return; } if (NULL == strpbrk(source, " \t\"")) { - strcpy(target, source); + xstrlcpy(target, source, remain); return; } if (NULL == strpbrk(source, "\"\\")) { - *(target++) = '"'; - strncpy(target, source, len); - target += len; - *(target++) = '"'; - *target = NUL; + snprintf(target, remain, "\"%s\"", source); return; } + assert(remain--); *(target++) = NUL; + assert(remain--); *(target++) = '"'; - for (i = len; i > 0; --i) { + for (i = src_len; i > 0; i--) { + assert(remain--); *(target++) = source[i - 1]; if (quote_hit && source[i - 1] == '\\') { + assert(remain--); *(target++) = '\\'; } else if (source[i - 1] == '"') { quote_hit = true; + assert(remain--); *(target++) = '\\'; } else { quote_hit = false; } } + assert(remain); *target = '"'; while (start < target) { tmp = *start; From 1614e805b33bf159a7af06ed54a0fb5823d8e407 Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 31 Mar 2017 05:40:37 +0900 Subject: [PATCH 06/16] win/test: tty-test: print screen size explicitly with CTRL-Q tty-test.exe causes abnormal termination with low repeatability, try changing it so as not to use SIGWINCH. --- test/functional/fixtures/tty-test.c | 53 ++++++++++++-------- test/functional/terminal/helpers.lua | 7 ++- test/functional/terminal/scrollback_spec.lua | 18 +++---- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index dd94d1a256..3e71ca209b 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -15,11 +15,11 @@ uv_tty_t tty; #include bool owns_tty(void) { - /* XXX: We need to make proper detect owns tty */ - /* HWND consoleWnd = GetConsoleWindow(); */ - /* DWORD dwProcessId; */ - /* GetWindowThreadProcessId(consoleWnd, &dwProcessId); */ - /* return GetCurrentProcessId() == dwProcessId; */ + // XXX: We need to make proper detect owns tty + // HWND consoleWnd = GetConsoleWindow(); + // DWORD dwProcessId; + // GetWindowThreadProcessId(consoleWnd, &dwProcessId); + // return GetCurrentProcessId() == dwProcessId; return true; } #else @@ -57,15 +57,15 @@ static void sig_handler(int signum) } } #else -static void sigwinch_cb(uv_signal_t *handle, int signum) -{ - int width, height; - uv_tty_t out; - uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); - uv_tty_get_winsize(&out, &width, &height); - fprintf(stderr, "rows: %d, cols: %d\n", height, width); - uv_close((uv_handle_t *)&out, NULL); -} +// static void sigwinch_cb(uv_signal_t *handle, int signum) +// { +// int width, height; +// uv_tty_t out; +// uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); +// uv_tty_get_winsize(&out, &width, &height); +// fprintf(stderr, "rows: %d, cols: %d\n", height, width); +// uv_close((uv_handle_t *)&out, NULL); +// } #endif static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) @@ -82,10 +82,14 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) } int *interrupted = stream->data; + bool prsz = false; + int width, height; for (int i = 0; i < cnt; i++) { if (buf->base[i] == 3) { (*interrupted)++; + } else if (buf->base[i] == 17) { + prsz = true; } } @@ -93,10 +97,17 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_loop_init(&write_loop); uv_tty_t out; uv_tty_init(&write_loop, &out, fileno(stdout), 0); - uv_write_t req; - uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; - uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); - uv_run(&write_loop, UV_RUN_DEFAULT); + + if (prsz) { + uv_tty_get_winsize(&out, &width, &height); + fprintf(stderr, "rows: %d, cols: %d\n", height, width); + } else { + uv_write_t req; + uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; + uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); + uv_run(&write_loop, UV_RUN_DEFAULT); + } + uv_close(STRUCT_CAST(uv_handle_t, &out), NULL); uv_run(&write_loop, UV_RUN_DEFAULT); if (uv_loop_close(&write_loop)) { @@ -169,9 +180,9 @@ int main(int argc, char **argv) sigaction(SIGHUP, &sa, NULL); sigaction(SIGWINCH, &sa, NULL); #else - uv_signal_t sigwinch_watcher; - uv_signal_init(uv_default_loop(), &sigwinch_watcher); - uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); + // uv_signal_t sigwinch_watcher; + // uv_signal_init(uv_default_loop(), &sigwinch_watcher); + // uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); #endif uv_run(uv_default_loop(), UV_RUN_DEFAULT); diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index 29381ab4f0..c67640f048 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -30,10 +30,9 @@ local function clear_attrs() feed_termcode('[0;10m') end -- mouse local function enable_mouse() feed_termcode('[?1002h') end local function disable_mouse() feed_termcode('[?1002l') end -local function wait_sigwinch() +local function print_screen_size() helpers.sleep(1000) - hide_cursor() - show_cursor() + nvim('command', 'call jobsend(b:terminal_job_id, "\\")') end local default_command = '["'..nvim_dir..'/tty-test'..'"]' @@ -116,6 +115,6 @@ return { clear_attrs = clear_attrs, enable_mouse = enable_mouse, disable_mouse = disable_mouse, - wait_sigwinch = wait_sigwinch, + print_screen_size = print_screen_size, screen_setup = screen_setup } diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 649bb4373b..1333736376 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.feed_command -local iswin, wait_sigwinch = helpers.iswin, thelpers.wait_sigwinch +local iswin, print_screen_size = helpers.iswin, thelpers.print_screen_size local eval = helpers.eval local command = helpers.command local wait = helpers.wait @@ -142,7 +142,7 @@ describe('terminal scrollback', function() local function will_hide_top_line() screen:try_resize(screen._width, screen._height - 1) if iswin() then - wait_sigwinch() + print_screen_size() end screen:expect([[ line2 | @@ -161,7 +161,7 @@ describe('terminal scrollback', function() will_hide_top_line() screen:try_resize(screen._width, screen._height - 2) if iswin() then - wait_sigwinch() + print_screen_size() end end) @@ -190,7 +190,7 @@ describe('terminal scrollback', function() before_each(function() screen:try_resize(screen._width, screen._height - 2) if iswin() then - wait_sigwinch() + print_screen_size() end end) @@ -214,7 +214,7 @@ describe('terminal scrollback', function() will_delete_last_two_lines() screen:try_resize(screen._width, screen._height - 1) if iswin() then - wait_sigwinch() + print_screen_size() end end) @@ -259,7 +259,7 @@ describe('terminal scrollback', function() ]]) screen:try_resize(screen._width, screen._height - 3) if iswin() then - wait_sigwinch() + print_screen_size() end screen:expect([[ line4 | @@ -274,7 +274,7 @@ describe('terminal scrollback', function() local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) if iswin() then - wait_sigwinch() + print_screen_size() end screen:expect([[ line4 | @@ -293,7 +293,7 @@ describe('terminal scrollback', function() eq(8, curbuf('line_count')) screen:try_resize(screen._width, screen._height + 3) if iswin() then - wait_sigwinch() + print_screen_size() end end) @@ -331,7 +331,7 @@ describe('terminal scrollback', function() feed('Gi') screen:try_resize(screen._width, screen._height + 4) if iswin() then - wait_sigwinch() + print_screen_size() end end) From 84fb794da653fc3bcc2832816ca0cab01b4e0400 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sun, 2 Apr 2017 18:16:39 +0900 Subject: [PATCH 07/16] win/pyt: cleanup --- src/nvim/os/pty_process_win.c | 279 ++++++++++++++++++++-------------- src/nvim/os/pty_process_win.h | 13 +- 2 files changed, 176 insertions(+), 116 deletions(-) diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index f3efe87d78..6a08e2868b 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -12,19 +12,6 @@ # include "os/pty_process_win.c.generated.h" #endif -static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) - FUNC_ATTR_NONNULL_ALL -{ - PtyProcess *ptyproc = - (PtyProcess *)((uv_handle_t *)wait_eof_timer->data); - Process *proc = (Process *)ptyproc; - - if (!proc->out || !uv_is_readable(proc->out->uvstream)) { - uv_timer_stop(&ptyproc->wait_eof_timer); - pty_process_finish2(ptyproc); - } -} - static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) FUNC_ATTR_NONNULL_ALL { @@ -36,6 +23,7 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); } +/// @returns zero on sucess, or error code of winpty or MultiByteToWideChar. int pty_process_spawn(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { @@ -44,34 +32,39 @@ int pty_process_spawn(PtyProcess *ptyproc) winpty_error_ptr_t err = NULL; winpty_config_t *cfg = NULL; winpty_spawn_config_t *spawncfg = NULL; - winpty_t *wp = NULL; - char *in_name = NULL, *out_name = NULL; + winpty_t *winpty_object = NULL; + char *in_name = NULL; + char *out_name = NULL; HANDLE process_handle = NULL; - uv_connect_t *in_req = NULL, *out_req = NULL; - wchar_t *cmdline = NULL, *cwd = NULL; + uv_connect_t *in_req = NULL; + uv_connect_t *out_req = NULL; + wchar_t *cmd_line = NULL; + wchar_t *cwd = NULL; assert(!proc->err); - if (!(cfg = winpty_config_new( - WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err))) { - goto cleanup; - } - winpty_config_set_initial_size( - cfg, - ptyproc->width, - ptyproc->height); - - if (!(wp = winpty_open(cfg, &err))) { + cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); + if (cfg == NULL) { goto cleanup; } - if ((status = utf16_to_utf8(winpty_conin_name(wp), &in_name))) { + winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); + winpty_object = winpty_open(cfg, &err); + if (winpty_object == NULL) { goto cleanup; } - if ((status = utf16_to_utf8(winpty_conout_name(wp), &out_name))) { + + status = utf16_to_utf8(winpty_conin_name(winpty_object), &in_name); + if (status != 0) { goto cleanup; } - if (proc->in) { + + status = utf16_to_utf8(winpty_conout_name(winpty_object), &out_name); + if (status != 0) { + goto cleanup; + } + + if (proc->in != NULL) { in_req = xmalloc(sizeof(uv_connect_t)); uv_pipe_connect( in_req, @@ -79,7 +72,8 @@ int pty_process_spawn(PtyProcess *ptyproc) in_name, pty_process_connect_cb); } - if (proc->out) { + + if (proc->out != NULL) { out_req = xmalloc(sizeof(uv_connect_t)); uv_pipe_connect( out_req, @@ -88,46 +82,65 @@ int pty_process_spawn(PtyProcess *ptyproc) pty_process_connect_cb); } - if (proc->cwd != NULL && (status = utf8_to_utf16(proc->cwd, &cwd))) { + if (proc->cwd != NULL) { + status = utf8_to_utf16(proc->cwd, &cwd); + if (status != 0) { + goto cleanup; + } + } + + status = build_cmd_line(proc->argv, &cmd_line); + if (status != 0) { goto cleanup; } - if ((status = build_cmdline(proc->argv, &cmdline))) { - goto cleanup; - } - if (!(spawncfg = winpty_spawn_config_new( + + spawncfg = winpty_spawn_config_new( WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, - NULL, cmdline, cwd, NULL, &err))) { + NULL, // Optional application name + cmd_line, + cwd, + NULL, // Optional environment variables + &err); + if (spawncfg == NULL) { goto cleanup; } - if (!winpty_spawn(wp, spawncfg, &process_handle, NULL, NULL, &err)) { + + if (!winpty_spawn(winpty_object, + spawncfg, + &process_handle, + NULL, // Optional thread handle + NULL, // Optional create process error + &err)) { goto cleanup; } proc->pid = GetProcessId(process_handle); if (!RegisterWaitForSingleObject( &ptyproc->finish_wait, - process_handle, pty_process_finish1, ptyproc, - INFINITE, WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) { + process_handle, + pty_process_finish1, + ptyproc, + INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) { abort(); } - while ((in_req && in_req->handle) || (out_req && out_req->handle)) { + // Wait until pty_process_connect_cb is called. + while ((in_req != NULL && in_req->handle != NULL) + || (out_req != NULL && out_req->handle != NULL)) { uv_run(&proc->loop->uv, UV_RUN_ONCE); } - ptyproc->wp = wp; + ptyproc->winpty_object = winpty_object; ptyproc->process_handle = process_handle; - wp = NULL; + winpty_object = NULL; process_handle = NULL; cleanup: - if (err != NULL) { - status = (int)winpty_error_code(err); - } winpty_error_free(err); winpty_config_free(cfg); winpty_spawn_config_free(spawncfg); - winpty_free(wp); + winpty_free(winpty_object); xfree(in_name); xfree(out_name); if (process_handle != NULL) { @@ -135,7 +148,7 @@ cleanup: } xfree(in_req); xfree(out_req); - xfree(cmdline); + xfree(cmd_line); xfree(cwd); return status; } @@ -144,8 +157,8 @@ void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->wp != NULL) { - winpty_set_size(ptyproc->wp, width, height, NULL); + if (ptyproc->winpty_object != NULL) { + winpty_set_size(ptyproc->winpty_object, width, height, NULL); } } @@ -164,9 +177,9 @@ void pty_process_close(PtyProcess *ptyproc) void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->wp != NULL) { - winpty_free(ptyproc->wp); - ptyproc->wp = NULL; + if (ptyproc->winpty_object != NULL) { + winpty_free(ptyproc->winpty_object); + ptyproc->winpty_object = NULL; } } @@ -182,6 +195,19 @@ static void pty_process_connect_cb(uv_connect_t *req, int status) req->handle = NULL; } +static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) + FUNC_ATTR_NONNULL_ALL +{ + PtyProcess *ptyproc = + (PtyProcess *)((uv_handle_t *)wait_eof_timer->data); + Process *proc = (Process *)ptyproc; + + if (!proc->out || !uv_is_readable(proc->out->uvstream)) { + uv_timer_stop(&ptyproc->wait_eof_timer); + pty_process_finish2(ptyproc); + } +} + static void pty_process_finish2(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { @@ -200,99 +226,132 @@ static void pty_process_finish2(PtyProcess *ptyproc) proc->internal_exit_cb(proc); } -static int build_cmdline(char **argv, wchar_t **cmdline) +/// Build the command line to pass to CreateProcessW. +/// +/// @param[in] argv Array with string arguments. +/// @param[out] cmd_line Location where saved bulded cmd line. +/// +/// @returns zero on sucess, or error code of MultiByteToWideChar function. +/// +static int build_cmd_line(char **argv, wchar_t **cmd_line) FUNC_ATTR_NONNULL_ALL { - char *args = NULL; - size_t args_len = 0, argc = 0; - int ret; - QUEUE q; - QUEUE_INIT(&q); + size_t utf8_cmd_line_len = 0; + size_t argc = 0; + QUEUE args_q; + QUEUE_INIT(&args_q); while (*argv) { size_t buf_len = strlen(*argv) * 2 + 3; - arg_T *arg = xmalloc(sizeof(arg_T)); - arg->arg = (char *)xmalloc(buf_len); - quote_cmd_arg(arg->arg, buf_len, *argv); - args_len += strlen(arg->arg); - QUEUE_INIT(&arg->node); - QUEUE_INSERT_TAIL(&q, &arg->node); + ArgNode *arg_node = xmalloc(sizeof(*arg_node)); + arg_node->arg = xmalloc(buf_len); + quote_cmd_arg(arg_node->arg, buf_len, *argv); + utf8_cmd_line_len += strlen(arg_node->arg); + QUEUE_INIT(&arg_node->node); + QUEUE_INSERT_TAIL(&args_q, &arg_node->node); argc++; argv++; } - args_len += argc; - args = xmalloc(args_len); - *args = NUL; + + utf8_cmd_line_len += argc; + char *utf8_cmd_line = xmalloc(utf8_cmd_line_len); + *utf8_cmd_line = NUL; while (1) { - QUEUE *head = QUEUE_HEAD(&q); + QUEUE *head = QUEUE_HEAD(&args_q); QUEUE_REMOVE(head); - arg_T *arg = QUEUE_DATA(head, arg_T, node); - xstrlcat(args, arg->arg, args_len); - xfree(arg->arg); - xfree(arg); - if (QUEUE_EMPTY(&q)) { + ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node); + xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); + xfree(arg_node->arg); + xfree(arg_node); + if (QUEUE_EMPTY(&args_q)) { break; } else { - xstrlcat(args, " ", args_len); + xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); } } - ret = utf8_to_utf16(args, cmdline); - xfree(args); - return ret; + + int result = utf8_to_utf16(utf8_cmd_line, cmd_line); + if (result != 0) { + } + xfree(utf8_cmd_line); + return result; } -// Emulate quote_cmd_arg of libuv and quotes command line arguments -static void quote_cmd_arg(char *target, size_t remain, const char *source) +/// Emulate quote_cmd_arg of libuv and quotes command line argument. +/// Most of the code came from libuv. +/// +/// @param[out] dist Location where saved quotes argument. +/// @param dist_remaining Deistnation buffer size. +/// @param[in] src Pointer to argument. +/// +static void quote_cmd_arg(char *dist, size_t dist_remaining, const char *src) FUNC_ATTR_NONNULL_ALL { - size_t src_len = strlen(source); - size_t i; + size_t src_len = strlen(src); bool quote_hit = true; - char *start = target; - char tmp; + char *start = dist; if (src_len == 0) { - snprintf(target, remain, "\"\""); + // Need double quotation for empty argument. + snprintf(dist, dist_remaining, "\"\""); return; } - if (NULL == strpbrk(source, " \t\"")) { - xstrlcpy(target, source, remain); + if (NULL == strpbrk(src, " \t\"")) { + // No quotation needed. + xstrlcpy(dist, src, dist_remaining); return; } - if (NULL == strpbrk(source, "\"\\")) { - snprintf(target, remain, "\"%s\"", source); + if (NULL == strpbrk(src, "\"\\")) { + // No embedded double quotes or backlashes, so I can just wrap quote marks. + // around the whole thing. + snprintf(dist, dist_remaining, "\"%s\"", src); return; } - assert(remain--); - *(target++) = NUL; - assert(remain--); - *(target++) = '"'; - for (i = src_len; i > 0; i--) { - assert(remain--); - *(target++) = source[i - 1]; + // Expected input/output: + // input : hello"world + // output: "hello\"world" + // input : hello""world + // output: "hello\"\"world" + // input : hello\world + // output: hello\world + // input : hello\\world + // output: hello\\world + // input : hello\"world + // output: "hello\\\"world" + // input : hello\\"world + // output: "hello\\\\\"world" + // input : hello world\ + // output: "hello world\\" - if (quote_hit && source[i - 1] == '\\') { - assert(remain--); - *(target++) = '\\'; - } else if (source[i - 1] == '"') { + assert(dist_remaining--); + *(dist++) = NUL; + assert(dist_remaining--); + *(dist++) = '"'; + for (size_t i = src_len; i > 0; i--) { + assert(dist_remaining--); + *(dist++) = src[i - 1]; + if (quote_hit && src[i - 1] == '\\') { + assert(dist_remaining--); + *(dist++) = '\\'; + } else if (src[i - 1] == '"') { quote_hit = true; - assert(remain--); - *(target++) = '\\'; + assert(dist_remaining--); + *(dist++) = '\\'; } else { quote_hit = false; } } - assert(remain); - *target = '"'; - while (start < target) { - tmp = *start; - *start = *target; - *target = tmp; + assert(dist_remaining); + *dist = '"'; + + while (start < dist) { + char tmp = *start; + *start = *dist; + *dist = tmp; start++; - target--; + dist--; } - return; } diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 806857f130..59e0fad7f7 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -12,16 +12,17 @@ typedef struct pty_process { Process process; char *term_name; uint16_t width, height; - winpty_t *wp; + winpty_t *winpty_object; HANDLE finish_wait; HANDLE process_handle; uv_timer_t wait_eof_timer; } PtyProcess; -typedef struct arg_S { - char *arg; - QUEUE node; -} arg_T; +// Structure used by build_cmd_line() +typedef struct arg_node { + char *arg; // pointer to argument. + QUEUE node; // QUEUE structure. +} ArgNode; static inline PtyProcess pty_process_init(Loop *loop, void *data) { @@ -30,7 +31,7 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.term_name = NULL; rv.width = 80; rv.height = 24; - rv.wp = NULL; + rv.winpty_object = NULL; rv.finish_wait = NULL; rv.process_handle = NULL; return rv; From d3a8c4f99289f7b65a68bf9ed5eeab34aa688e0e Mon Sep 17 00:00:00 2001 From: erw7 Date: Sun, 2 Apr 2017 18:32:23 +0900 Subject: [PATCH 08/16] win/pty: log errors --- src/nvim/os/pty_process_win.c | 112 +++++++++++++------ src/nvim/os/pty_process_win.h | 1 - test/functional/fixtures/tty-test.c | 64 ++++++++--- test/functional/terminal/helpers.lua | 5 - test/functional/terminal/scrollback_spec.lua | 36 +----- test/functional/ui/screen.lua | 13 +++ 6 files changed, 150 insertions(+), 81 deletions(-) diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 6a08e2868b..4bb5f64455 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -2,7 +2,9 @@ #include #include -#include "nvim/vim.h" +#include + +#include "nvim/os/os.h" #include "nvim/ascii.h" #include "nvim/memory.h" #include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 @@ -23,7 +25,7 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); } -/// @returns zero on sucess, or error code of winpty or MultiByteToWideChar. +/// @returns zero on success, or negative error code. int pty_process_spawn(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { @@ -40,27 +42,32 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_connect_t *out_req = NULL; wchar_t *cmd_line = NULL; wchar_t *cwd = NULL; + const char *emsg = NULL; assert(!proc->err); cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); if (cfg == NULL) { + emsg = "Failed, winpty_config_new."; goto cleanup; } winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); winpty_object = winpty_open(cfg, &err); if (winpty_object == NULL) { + emsg = "Failed, winpty_open."; goto cleanup; } status = utf16_to_utf8(winpty_conin_name(winpty_object), &in_name); if (status != 0) { + emsg = "Failed to convert in_name from utf16 to utf8."; goto cleanup; } status = utf16_to_utf8(winpty_conout_name(winpty_object), &out_name); if (status != 0) { + emsg = "Failed to convert out_name from utf16 to utf8."; goto cleanup; } @@ -85,12 +92,14 @@ int pty_process_spawn(PtyProcess *ptyproc) if (proc->cwd != NULL) { status = utf8_to_utf16(proc->cwd, &cwd); if (status != 0) { + emsg = "Failed to convert pwd form utf8 to utf16."; goto cleanup; } } status = build_cmd_line(proc->argv, &cmd_line); if (status != 0) { + emsg = "Failed to convert cmd line form utf8 to utf16."; goto cleanup; } @@ -102,15 +111,23 @@ int pty_process_spawn(PtyProcess *ptyproc) NULL, // Optional environment variables &err); if (spawncfg == NULL) { + emsg = "Failed winpty_spawn_config_new."; goto cleanup; } + DWORD win_err = 0; if (!winpty_spawn(winpty_object, spawncfg, &process_handle, NULL, // Optional thread handle - NULL, // Optional create process error + &win_err, &err)) { + if (win_err) { + status = (int)win_err; + emsg = "Failed spawn process."; + } else { + emsg = "Failed winpty_spawn."; + } goto cleanup; } proc->pid = GetProcessId(process_handle); @@ -137,6 +154,15 @@ int pty_process_spawn(PtyProcess *ptyproc) process_handle = NULL; cleanup: + if (status) { + // In the case of an error of MultiByteToWideChar or CreateProcessW. + ELOG("%s error code: %d", emsg, status); + status = os_translate_sys_error(status); + } else if (err != NULL) { + status = (int)winpty_error_code(err); + ELOG("%s error code: %d", emsg, status); + status = translate_winpty_error(status); + } winpty_error_free(err); winpty_config_free(cfg); winpty_spawn_config_free(spawncfg); @@ -198,8 +224,7 @@ static void pty_process_connect_cb(uv_connect_t *req, int status) static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) FUNC_ATTR_NONNULL_ALL { - PtyProcess *ptyproc = - (PtyProcess *)((uv_handle_t *)wait_eof_timer->data); + PtyProcess *ptyproc = wait_eof_timer->data; Process *proc = (Process *)ptyproc; if (!proc->out || !uv_is_readable(proc->out->uvstream)) { @@ -229,9 +254,9 @@ static void pty_process_finish2(PtyProcess *ptyproc) /// Build the command line to pass to CreateProcessW. /// /// @param[in] argv Array with string arguments. -/// @param[out] cmd_line Location where saved bulded cmd line. +/// @param[out] cmd_line Location where saved builded cmd line. /// -/// @returns zero on sucess, or error code of MultiByteToWideChar function. +/// @returns zero on success, or error code of MultiByteToWideChar function. /// static int build_cmd_line(char **argv, wchar_t **cmd_line) FUNC_ATTR_NONNULL_ALL @@ -271,8 +296,6 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line) } int result = utf8_to_utf16(utf8_cmd_line, cmd_line); - if (result != 0) { - } xfree(utf8_cmd_line); return result; } @@ -280,33 +303,33 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line) /// Emulate quote_cmd_arg of libuv and quotes command line argument. /// Most of the code came from libuv. /// -/// @param[out] dist Location where saved quotes argument. -/// @param dist_remaining Deistnation buffer size. +/// @param[out] dest Location where saved quotes argument. +/// @param dest_remaining Destination buffer size. /// @param[in] src Pointer to argument. /// -static void quote_cmd_arg(char *dist, size_t dist_remaining, const char *src) +static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src) FUNC_ATTR_NONNULL_ALL { size_t src_len = strlen(src); bool quote_hit = true; - char *start = dist; + char *start = dest; if (src_len == 0) { // Need double quotation for empty argument. - snprintf(dist, dist_remaining, "\"\""); + snprintf(dest, dest_remaining, "\"\""); return; } if (NULL == strpbrk(src, " \t\"")) { // No quotation needed. - xstrlcpy(dist, src, dist_remaining); + xstrlcpy(dest, src, dest_remaining); return; } if (NULL == strpbrk(src, "\"\\")) { // No embedded double quotes or backlashes, so I can just wrap quote marks. // around the whole thing. - snprintf(dist, dist_remaining, "\"%s\"", src); + snprintf(dest, dest_remaining, "\"%s\"", src); return; } @@ -326,32 +349,57 @@ static void quote_cmd_arg(char *dist, size_t dist_remaining, const char *src) // input : hello world\ // output: "hello world\\" - assert(dist_remaining--); - *(dist++) = NUL; - assert(dist_remaining--); - *(dist++) = '"'; + assert(dest_remaining--); + *(dest++) = NUL; + assert(dest_remaining--); + *(dest++) = '"'; for (size_t i = src_len; i > 0; i--) { - assert(dist_remaining--); - *(dist++) = src[i - 1]; + assert(dest_remaining--); + *(dest++) = src[i - 1]; if (quote_hit && src[i - 1] == '\\') { - assert(dist_remaining--); - *(dist++) = '\\'; + assert(dest_remaining--); + *(dest++) = '\\'; } else if (src[i - 1] == '"') { quote_hit = true; - assert(dist_remaining--); - *(dist++) = '\\'; + assert(dest_remaining--); + *(dest++) = '\\'; } else { quote_hit = false; } } - assert(dist_remaining); - *dist = '"'; + assert(dest_remaining); + *dest = '"'; - while (start < dist) { + while (start < dest) { char tmp = *start; - *start = *dist; - *dist = tmp; + *start = *dest; + *dest = tmp; start++; - dist--; + dest--; + } +} + +/// Translate winpty error code to libuv error. +/// +/// @param[in] winpty_errno Winpty error code returned by winpty_error_code +/// function. +/// +/// @returns Error code of libuv error. +int translate_winpty_error(int winpty_errno) +{ + if (winpty_errno <= 0) { + return winpty_errno; // If < 0 then it's already a libuv error. + } + + switch (winpty_errno) { + case WINPTY_ERROR_OUT_OF_MEMORY: return UV_ENOMEM; + case WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED: return UV_EAI_FAIL; + case WINPTY_ERROR_LOST_CONNECTION: return UV_ENOTCONN; + case WINPTY_ERROR_AGENT_EXE_MISSING: return UV_ENOENT; + case WINPTY_ERROR_UNSPECIFIED: return UV_UNKNOWN; + case WINPTY_ERROR_AGENT_DIED: return UV_ESRCH; + case WINPTY_ERROR_AGENT_TIMEOUT: return UV_ETIMEDOUT; + case WINPTY_ERROR_AGENT_CREATION_FAILED: return UV_EAI_FAIL; + default: return UV_UNKNOWN; } } diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 59e0fad7f7..1a4019e654 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -2,7 +2,6 @@ #define NVIM_OS_PTY_PROCESS_WIN_H #include - #include #include "nvim/event/process.h" diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index 3e71ca209b..60d6f5485b 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -4,15 +4,34 @@ #include #include #include + #include +#ifdef _WIN32 +#include +#endif // -V:STRUCT_CAST:641 #define STRUCT_CAST(Type, obj) ((Type *)(obj)) - -uv_tty_t tty; +#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) +#define BUF_SIZE 0xfff +#define CTRL_C 0x03 +#ifdef _WIN32 +#define CTRL_Q 0x11 +#endif + +#ifdef _WIN32 +typedef struct screen_size { + int width; + int height; +} ScreenSize; +#endif + +uv_tty_t tty; +#ifdef _WIN32 +ScreenSize screen_rect; +#endif #ifdef _WIN32 -#include bool owns_tty(void) { // XXX: We need to make proper detect owns tty @@ -30,10 +49,8 @@ bool owns_tty(void) } #endif -#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) -#define BUF_SIZE 0xfff - -static void walk_cb(uv_handle_t *handle, void *arg) { +static void walk_cb(uv_handle_t *handle, void *arg) +{ if (!uv_is_closing(handle)) { uv_close(handle, NULL); } @@ -42,7 +59,7 @@ static void walk_cb(uv_handle_t *handle, void *arg) { #ifndef WIN32 static void sig_handler(int signum) { - switch(signum) { + switch (signum) { case SIGWINCH: { int width, height; uv_tty_get_winsize(&tty, &width, &height); @@ -82,14 +99,19 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) } int *interrupted = stream->data; +#ifdef _WIN32 bool prsz = false; - int width, height; + int width; + int height; +#endif for (int i = 0; i < cnt; i++) { - if (buf->base[i] == 3) { + if (buf->base[i] == CTRL_C) { (*interrupted)++; - } else if (buf->base[i] == 17) { +#ifdef _WIN32 + } else if (buf->base[i] == CTRL_Q) { prsz = true; +#endif } } @@ -98,15 +120,23 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_tty_t out; uv_tty_init(&write_loop, &out, fileno(stdout), 0); +#ifdef _WIN32 if (prsz) { uv_tty_get_winsize(&out, &width, &height); - fprintf(stderr, "rows: %d, cols: %d\n", height, width); + if (screen_rect.width != width || screen_rect.height != height) { + screen_rect.width = width; + screen_rect.height = height; + fprintf(stderr, "rows: %d, cols: %d\n", height, width); + } } else { +#endif uv_write_t req; uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); uv_run(&write_loop, UV_RUN_DEFAULT); +#ifdef _WIN32 } +#endif uv_close(STRUCT_CAST(uv_handle_t, &out), NULL); uv_run(&write_loop, UV_RUN_DEFAULT); @@ -152,7 +182,7 @@ int main(int argc, char **argv) if (argc > 1) { int count = atoi(argv[1]); - for (int i = 0; i < count; ++i) { + for (int i = 0; i < count; i++) { printf("line%d\n", i); } fflush(stdout); @@ -168,6 +198,14 @@ int main(int argc, char **argv) uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1); #else uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1); + uv_tty_t out; + uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); + int width; + int height; + uv_tty_get_winsize(&out, &width, &height); + screen_rect.width = width; + screen_rect.height = height; + uv_close((uv_handle_t *)&out, NULL); #endif uv_tty_set_mode(&tty, UV_TTY_MODE_RAW); tty.data = &interrupted; diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index c67640f048..bd24b9785d 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -30,10 +30,6 @@ local function clear_attrs() feed_termcode('[0;10m') end -- mouse local function enable_mouse() feed_termcode('[?1002h') end local function disable_mouse() feed_termcode('[?1002l') end -local function print_screen_size() - helpers.sleep(1000) - nvim('command', 'call jobsend(b:terminal_job_id, "\\")') -end local default_command = '["'..nvim_dir..'/tty-test'..'"]' @@ -115,6 +111,5 @@ return { clear_attrs = clear_attrs, enable_mouse = enable_mouse, disable_mouse = disable_mouse, - print_screen_size = print_screen_size, screen_setup = screen_setup } diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 1333736376..f39335bfd0 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.feed_command -local iswin, print_screen_size = helpers.iswin, thelpers.print_screen_size +local iswin = helpers.iswin local eval = helpers.eval local command = helpers.command local wait = helpers.wait @@ -141,10 +141,7 @@ describe('terminal scrollback', function() describe('and the height is decreased by 1', function() local function will_hide_top_line() screen:try_resize(screen._width, screen._height - 1) - if iswin() then - print_screen_size() - end - screen:expect([[ + screen:expect_after_resize([[ line2 | line3 | line4 | @@ -160,13 +157,10 @@ describe('terminal scrollback', function() before_each(function() will_hide_top_line() screen:try_resize(screen._width, screen._height - 2) - if iswin() then - print_screen_size() - end end) it('will hide the top 3 lines', function() - screen:expect([[ + screen:expect_after_resize([[ rows: 5, cols: 30 | rows: 3, cols: 30 | {1: } | @@ -189,9 +183,6 @@ describe('terminal scrollback', function() describe('and the height is decreased by 2', function() before_each(function() screen:try_resize(screen._width, screen._height - 2) - if iswin() then - print_screen_size() - end end) local function will_delete_last_two_lines() @@ -213,9 +204,6 @@ describe('terminal scrollback', function() before_each(function() will_delete_last_two_lines() screen:try_resize(screen._width, screen._height - 1) - if iswin() then - print_screen_size() - end end) it('will delete the last line and hide the first', function() @@ -258,10 +246,7 @@ describe('terminal scrollback', function() {3:-- TERMINAL --} | ]]) screen:try_resize(screen._width, screen._height - 3) - if iswin() then - print_screen_size() - end - screen:expect([[ + screen:expect_after_resize([[ line4 | rows: 3, cols: 30 | {1: } | @@ -273,10 +258,7 @@ describe('terminal scrollback', function() describe('and the height is increased by 1', function() local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) - if iswin() then - print_screen_size() - end - screen:expect([[ + screen:expect_after_resize([[ line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | @@ -292,13 +274,10 @@ describe('terminal scrollback', function() pop_then_push() eq(8, curbuf('line_count')) screen:try_resize(screen._width, screen._height + 3) - if iswin() then - print_screen_size() - end end) local function pop3_then_push1() - screen:expect([[ + screen:expect_after_resize([[ line2 | line3 | line4 | @@ -330,9 +309,6 @@ describe('terminal scrollback', function() pop3_then_push1() feed('Gi') screen:try_resize(screen._width, screen._height + 4) - if iswin() then - print_screen_size() - end end) it('will show all lines and leave a blank one at the end', function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index a6b7fb2997..77b69041e0 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -73,6 +73,8 @@ local helpers = require('test.functional.helpers')(nil) local request, run, uimeths = helpers.request, helpers.run, helpers.uimeths +local iswin, nvim, retry = helpers.iswin, helpers.nvim, helpers.retry + local dedent = helpers.dedent local Screen = {} @@ -259,6 +261,17 @@ screen:redraw_debug() to show all intermediate screen states. ]]) end) end +function Screen:expect_after_resize(expected) + if iswin() then + retry(nil, nil, function() + nvim('command', 'call jobsend(b:terminal_job_id, "\\")') + self:expect(expected) + end) + else + self:expect(expected) + end +end + function Screen:wait(check, timeout) local err, checked = false local success_seen = false From 8c1782b84070d786cc1a7dfd6242a9ebc0923ad3 Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 14 Apr 2017 22:35:56 +0900 Subject: [PATCH 09/16] pty_process_win: avoid quoting for cmd.exe --- src/nvim/os/pty_process_win.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 4bb5f64455..ef8a699c56 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -97,7 +97,8 @@ int pty_process_spawn(PtyProcess *ptyproc) } } - status = build_cmd_line(proc->argv, &cmd_line); + status = build_cmd_line(proc->argv, &cmd_line, + os_shell_is_cmdexe(proc->argv[0])); if (status != 0) { emsg = "Failed to convert cmd line form utf8 to utf16."; goto cleanup; @@ -258,7 +259,7 @@ static void pty_process_finish2(PtyProcess *ptyproc) /// /// @returns zero on success, or error code of MultiByteToWideChar function. /// -static int build_cmd_line(char **argv, wchar_t **cmd_line) +static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe) FUNC_ATTR_NONNULL_ALL { size_t utf8_cmd_line_len = 0; @@ -267,10 +268,14 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line) QUEUE_INIT(&args_q); while (*argv) { - size_t buf_len = strlen(*argv) * 2 + 3; + size_t buf_len = is_cmdexe ? (strlen(*argv) + 1) : (strlen(*argv) * 2 + 3); ArgNode *arg_node = xmalloc(sizeof(*arg_node)); arg_node->arg = xmalloc(buf_len); - quote_cmd_arg(arg_node->arg, buf_len, *argv); + if (is_cmdexe) { + xstrlcpy(arg_node->arg, *argv, buf_len); + } else { + quote_cmd_arg(arg_node->arg, buf_len, *argv); + } utf8_cmd_line_len += strlen(arg_node->arg); QUEUE_INIT(&arg_node->node); QUEUE_INSERT_TAIL(&args_q, &arg_node->node); From 8642f05fd91d7285a7d8d2bb13b4d63fe5c371fa Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 Aug 2017 14:28:51 +0200 Subject: [PATCH 10/16] single-includes: ignore os/pty_process_win.h --- src/nvim/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 2e0a35d4ab..688912eda6 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -535,6 +535,7 @@ endfunction() set(NO_SINGLE_CHECK_HEADERS os/win_defs.h + os/pty_process_win.h regexp_defs.h syntax_defs.h terminal.h From 6a90f53862d9f75f7aeea350944c466aa85f11a2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 Aug 2017 14:26:17 +0200 Subject: [PATCH 11/16] test: cleanup --- test/functional/fixtures/tty-test.c | 48 +++++++------------- test/functional/terminal/scrollback_spec.lua | 20 +++++--- test/functional/ui/screen.lua | 21 ++++----- 3 files changed, 39 insertions(+), 50 deletions(-) diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index 60d6f5485b..2b22352cda 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -4,10 +4,11 @@ #include #include #include - #include #ifdef _WIN32 -#include +# include +#else +# include #endif // -V:STRUCT_CAST:641 @@ -15,39 +16,31 @@ #define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) #define BUF_SIZE 0xfff #define CTRL_C 0x03 -#ifdef _WIN32 #define CTRL_Q 0x11 -#endif + +uv_tty_t tty; #ifdef _WIN32 typedef struct screen_size { int width; int height; } ScreenSize; -#endif - -uv_tty_t tty; -#ifdef _WIN32 ScreenSize screen_rect; #endif -#ifdef _WIN32 bool owns_tty(void) { +#ifdef _WIN32 // XXX: We need to make proper detect owns tty // HWND consoleWnd = GetConsoleWindow(); // DWORD dwProcessId; // GetWindowThreadProcessId(consoleWnd, &dwProcessId); // return GetCurrentProcessId() == dwProcessId; return true; -} #else -#include -bool owns_tty(void) -{ return getsid(0) == getpid(); -} #endif +} static void walk_cb(uv_handle_t *handle, void *arg) { @@ -56,7 +49,6 @@ static void walk_cb(uv_handle_t *handle, void *arg) } } -#ifndef WIN32 static void sig_handler(int signum) { switch (signum) { @@ -73,17 +65,6 @@ static void sig_handler(int signum) return; } } -#else -// static void sigwinch_cb(uv_signal_t *handle, int signum) -// { -// int width, height; -// uv_tty_t out; -// uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); -// uv_tty_get_winsize(&out, &width, &height); -// fprintf(stderr, "rows: %d, cols: %d\n", height, width); -// uv_close((uv_handle_t *)&out, NULL); -// } -#endif static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { @@ -100,9 +81,9 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) int *interrupted = stream->data; #ifdef _WIN32 - bool prsz = false; - int width; - int height; + // HACK: Special-case to avoid relying on SIGWINCH on Windows. + // See note at Screen:try_resize(). + bool invoke_sigwinch_handler = false; #endif for (int i = 0; i < cnt; i++) { @@ -110,7 +91,7 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) (*interrupted)++; #ifdef _WIN32 } else if (buf->base[i] == CTRL_Q) { - prsz = true; + invoke_sigwinch_handler = true; #endif } } @@ -121,12 +102,15 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_tty_init(&write_loop, &out, fileno(stdout), 0); #ifdef _WIN32 - if (prsz) { + if (invoke_sigwinch_handler) { + int width, height; uv_tty_get_winsize(&out, &width, &height); if (screen_rect.width != width || screen_rect.height != height) { screen_rect.width = width; screen_rect.height = height; - fprintf(stderr, "rows: %d, cols: %d\n", height, width); + // HACK: Invoke directly. See note at Screen:try_resize(). + sig_handler(SIGWINCH); + uv_run(&write_loop, UV_RUN_NOWAIT); } } else { #endif diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index f39335bfd0..b6b0228513 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -141,7 +141,8 @@ describe('terminal scrollback', function() describe('and the height is decreased by 1', function() local function will_hide_top_line() screen:try_resize(screen._width, screen._height - 1) - screen:expect_after_resize([[ + retry(nil, 100000, function() + screen:expect([[ line2 | line3 | line4 | @@ -149,6 +150,7 @@ describe('terminal scrollback', function() {1: } | {3:-- TERMINAL --} | ]]) + end) end it('will hide top line', will_hide_top_line) @@ -160,7 +162,8 @@ describe('terminal scrollback', function() end) it('will hide the top 3 lines', function() - screen:expect_after_resize([[ + retry(nil, 100000, function() + screen:expect([[ rows: 5, cols: 30 | rows: 3, cols: 30 | {1: } | @@ -174,19 +177,21 @@ describe('terminal scrollback', function() rows: 3, cols: 30 | | ]]) + end) end) end) end) end) describe('with empty lines after the cursor', function() + if helpers.pending_win32(pending) then return end + describe('and the height is decreased by 2', function() before_each(function() screen:try_resize(screen._width, screen._height - 2) end) local function will_delete_last_two_lines() - if helpers.pending_win32(pending) then return end screen:expect([[ tty ready | rows: 4, cols: 30 | @@ -200,7 +205,6 @@ describe('terminal scrollback', function() it('will delete the last two empty lines', will_delete_last_two_lines) describe('and then decreased by 1', function() - if helpers.pending_win32(pending) then return end before_each(function() will_delete_last_two_lines() screen:try_resize(screen._width, screen._height - 1) @@ -246,19 +250,21 @@ describe('terminal scrollback', function() {3:-- TERMINAL --} | ]]) screen:try_resize(screen._width, screen._height - 3) - screen:expect_after_resize([[ + retry(nil, 100000, function() + screen:expect([[ line4 | rows: 3, cols: 30 | {1: } | {3:-- TERMINAL --} | ]]) eq(7, curbuf('line_count')) + end) end) describe('and the height is increased by 1', function() local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) - screen:expect_after_resize([[ + screen:expect([[ line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | @@ -277,7 +283,7 @@ describe('terminal scrollback', function() end) local function pop3_then_push1() - screen:expect_after_resize([[ + screen:expect([[ line2 | line3 | line4 | diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 77b69041e0..c1df7ede43 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -176,6 +176,16 @@ function Screen:try_resize(columns, rows) -- Give ourselves a chance to _handle_resize, which requires using -- self.sleep() (for the resize notification) rather than run() self:sleep(0.1) + + -- XXX: On Windows we don't bother to handle SIGWINCH. + -- CTRL-Q invokes the handler in tty-test.c directly. + -- uv_tty_update_virtual_window() _does_ emit SIGWINCH, but: + -- "SIGWINCH may not always be delivered in a timely manner; libuv + -- will only detect size changes when the cursor is being moved." + -- http://docs.libuv.org/en/v1.x/signal.html + if iswin() and 0 ~= nvim('eval', "exists('b:terminal_job_id')") then + nvim('command', [[call jobsend(b:terminal_job_id, "\")]]) + end end -- Asserts that `expected` eventually matches the screen state. @@ -261,17 +271,6 @@ screen:redraw_debug() to show all intermediate screen states. ]]) end) end -function Screen:expect_after_resize(expected) - if iswin() then - retry(nil, nil, function() - nvim('command', 'call jobsend(b:terminal_job_id, "\\")') - self:expect(expected) - end) - else - self:expect(expected) - end -end - function Screen:wait(check, timeout) local err, checked = false local success_seen = false From e0763e94ade67c99f9d9f46cd51299b174969927 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 7 Aug 2017 00:26:43 +0200 Subject: [PATCH 12/16] test: tty-test.c: restore win32 SIGWINCH handler --- test/functional/fixtures/tty-test.c | 50 ++++++++++++-------- test/functional/terminal/scrollback_spec.lua | 14 +++--- test/functional/ui/screen.lua | 12 ----- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index 2b22352cda..9373e2d7ed 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -49,6 +49,7 @@ static void walk_cb(uv_handle_t *handle, void *arg) } } +#ifndef WIN32 static void sig_handler(int signum) { switch (signum) { @@ -65,6 +66,17 @@ static void sig_handler(int signum) return; } } +#else +static void sigwinch_cb(uv_signal_t *handle, int signum) +{ + int width, height; + uv_tty_t out; + uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); + uv_tty_get_winsize(&out, &width, &height); + fprintf(stderr, "rows: %d, cols: %d\n", height, width); + uv_close((uv_handle_t *)&out, NULL); +} +#endif static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { @@ -101,26 +113,26 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_tty_t out; uv_tty_init(&write_loop, &out, fileno(stdout), 0); -#ifdef _WIN32 - if (invoke_sigwinch_handler) { - int width, height; - uv_tty_get_winsize(&out, &width, &height); - if (screen_rect.width != width || screen_rect.height != height) { - screen_rect.width = width; - screen_rect.height = height; - // HACK: Invoke directly. See note at Screen:try_resize(). - sig_handler(SIGWINCH); - uv_run(&write_loop, UV_RUN_NOWAIT); - } - } else { -#endif +// #ifdef _WIN32 +// if (invoke_sigwinch_handler) { +// int width, height; +// uv_tty_get_winsize(&out, &width, &height); +// if (screen_rect.width != width || screen_rect.height != height) { +// screen_rect.width = width; +// screen_rect.height = height; +// // HACK: Invoke directly. See note at Screen:try_resize(). +// sig_handler(SIGWINCH); +// uv_run(&write_loop, UV_RUN_NOWAIT); +// } +// } else { +// #endif uv_write_t req; uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); uv_run(&write_loop, UV_RUN_DEFAULT); -#ifdef _WIN32 - } -#endif +// #ifdef _WIN32 +// } +// #endif uv_close(STRUCT_CAST(uv_handle_t, &out), NULL); uv_run(&write_loop, UV_RUN_DEFAULT); @@ -202,9 +214,9 @@ int main(int argc, char **argv) sigaction(SIGHUP, &sa, NULL); sigaction(SIGWINCH, &sa, NULL); #else - // uv_signal_t sigwinch_watcher; - // uv_signal_init(uv_default_loop(), &sigwinch_watcher); - // uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); + uv_signal_t sigwinch_watcher; + uv_signal_init(uv_default_loop(), &sigwinch_watcher); + uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); #endif uv_run(uv_default_loop(), UV_RUN_DEFAULT); diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index b6b0228513..f804862996 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -141,7 +141,6 @@ describe('terminal scrollback', function() describe('and the height is decreased by 1', function() local function will_hide_top_line() screen:try_resize(screen._width, screen._height - 1) - retry(nil, 100000, function() screen:expect([[ line2 | line3 | @@ -150,7 +149,6 @@ describe('terminal scrollback', function() {1: } | {3:-- TERMINAL --} | ]]) - end) end it('will hide top line', will_hide_top_line) @@ -162,7 +160,6 @@ describe('terminal scrollback', function() end) it('will hide the top 3 lines', function() - retry(nil, 100000, function() screen:expect([[ rows: 5, cols: 30 | rows: 3, cols: 30 | @@ -177,13 +174,15 @@ describe('terminal scrollback', function() rows: 3, cols: 30 | | ]]) - end) end) end) end) end) describe('with empty lines after the cursor', function() + -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ + -- by the resize. http://docs.libuv.org/en/v1.x/signal.html + -- See also: https://github.com/rprichard/winpty/issues/110 if helpers.pending_win32(pending) then return end describe('and the height is decreased by 2', function() @@ -250,7 +249,6 @@ describe('terminal scrollback', function() {3:-- TERMINAL --} | ]]) screen:try_resize(screen._width, screen._height - 3) - retry(nil, 100000, function() screen:expect([[ line4 | rows: 3, cols: 30 | @@ -258,10 +256,13 @@ describe('terminal scrollback', function() {3:-- TERMINAL --} | ]]) eq(7, curbuf('line_count')) - end) end) describe('and the height is increased by 1', function() + -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ + -- by the resize. http://docs.libuv.org/en/v1.x/signal.html + -- See also: https://github.com/rprichard/winpty/issues/110 + if helpers.pending_win32(pending) then return end local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) screen:expect([[ @@ -310,7 +311,6 @@ describe('terminal scrollback', function() it('will pop 3 lines and then push one back', pop3_then_push1) describe('and then by 4', function() - if helpers.pending_win32(pending) then return end before_each(function() pop3_then_push1() feed('Gi') diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index c1df7ede43..a6b7fb2997 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -73,8 +73,6 @@ local helpers = require('test.functional.helpers')(nil) local request, run, uimeths = helpers.request, helpers.run, helpers.uimeths -local iswin, nvim, retry = helpers.iswin, helpers.nvim, helpers.retry - local dedent = helpers.dedent local Screen = {} @@ -176,16 +174,6 @@ function Screen:try_resize(columns, rows) -- Give ourselves a chance to _handle_resize, which requires using -- self.sleep() (for the resize notification) rather than run() self:sleep(0.1) - - -- XXX: On Windows we don't bother to handle SIGWINCH. - -- CTRL-Q invokes the handler in tty-test.c directly. - -- uv_tty_update_virtual_window() _does_ emit SIGWINCH, but: - -- "SIGWINCH may not always be delivered in a timely manner; libuv - -- will only detect size changes when the cursor is being moved." - -- http://docs.libuv.org/en/v1.x/signal.html - if iswin() and 0 ~= nvim('eval', "exists('b:terminal_job_id')") then - nvim('command', [[call jobsend(b:terminal_job_id, "\")]]) - end end -- Asserts that `expected` eventually matches the screen state. From d2d76882f74875d6c1ee10f31524b104eab6375d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 8 Aug 2017 02:26:25 +0200 Subject: [PATCH 13/16] win/test: enable more :terminal tests To deal with SIGWINCH limitations on Windows, change some resize tests to _shrink_ the screen width. ... But this didn't work, so still ignoring those tests on Windows. --- test/functional/terminal/buffer_spec.lua | 2 - test/functional/terminal/ex_terminal_spec.lua | 2 - .../terminal/window_split_tab_spec.lua | 44 ++++++++----------- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 22ab0a8c21..4ce33fef7b 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -70,7 +70,6 @@ describe('terminal buffer', function() end) it('cannot be modified directly', function() - if helpers.pending_win32(pending) then return end feed('dd') screen:expect([[ tty ready | @@ -205,7 +204,6 @@ describe('terminal buffer', function() end) describe('No heap-buffer-overflow when using', function() - if helpers.pending_win32(pending) then return end local testfilename = 'Xtestfile-functional-terminal-buffers_spec' before_each(function() diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 5eb6a5f18c..9930efc402 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -182,9 +182,7 @@ describe(':terminal (with fake shell)', function() end) it('works with gf', function() - if helpers.pending_win32(pending) then return end terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) - wait() screen:expect([[ ready $ echo "scripts/shadacat.py" | | diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 4867e0d9fa..27c6c54d19 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -4,8 +4,6 @@ local clear = helpers.clear local feed, nvim = helpers.feed, helpers.nvim local feed_command = helpers.feed_command -if helpers.pending_win32(pending) then return end - describe('terminal', function() local screen @@ -25,6 +23,7 @@ describe('terminal', function() end) it('resets its size when entering terminal window', function() + if helpers.pending_win32(pending) then return end feed('') feed_command('2split') screen:expect([[ @@ -69,31 +68,26 @@ describe('terminal', function() describe('when the screen is resized', function() it('will forward a resize request to the program', function() - screen:try_resize(screen._width + 3, screen._height + 5) - screen:expect([[ - tty ready | - rows: 14, cols: 53 | - {1: } | - | - | - | - | - | - | - | - | - | - | - | - {3:-- TERMINAL --} | - ]]) - screen:try_resize(screen._width - 6, screen._height - 10) + if helpers.pending_win32(pending) then return end + feed([[:]]) -- Go to cmdline-mode, so cursor is at bottom. + screen:try_resize(screen._width - 3, screen._height - 2) screen:expect([[ tty ready | - rows: 14, cols: 53 | - rows: 4, cols: 47 | - {1: } | - {3:-- TERMINAL --} | + rows: 7, cols: 47 | + {2: } | + | + | + | + | + :^ | + ]]) + screen:try_resize(screen._width - 6, screen._height - 3) + screen:expect([[ + tty ready | + rows: 7, cols: 47 | + rows: 4, cols: 41 | + {2: } | + :^ | ]]) end) end) From 91c85a6378e93b6ef0415c47ca7cb03bf1d57e2d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 13 Aug 2017 15:24:25 +0200 Subject: [PATCH 14/16] test: tty-test.c: keep `tty_out` handle around Now the window_split_tab_spec.lua test seems to work. Also do some cleanup. --- test/functional/fixtures/tty-test.c | 64 ++++--------------- .../terminal/window_split_tab_spec.lua | 1 - 2 files changed, 11 insertions(+), 54 deletions(-) diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c index 9373e2d7ed..edcbe23f86 100644 --- a/test/functional/fixtures/tty-test.c +++ b/test/functional/fixtures/tty-test.c @@ -16,17 +16,9 @@ #define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY) #define BUF_SIZE 0xfff #define CTRL_C 0x03 -#define CTRL_Q 0x11 uv_tty_t tty; - -#ifdef _WIN32 -typedef struct screen_size { - int width; - int height; -} ScreenSize; -ScreenSize screen_rect; -#endif +uv_tty_t tty_out; bool owns_tty(void) { @@ -49,7 +41,6 @@ static void walk_cb(uv_handle_t *handle, void *arg) } } -#ifndef WIN32 static void sig_handler(int signum) { switch (signum) { @@ -66,15 +57,13 @@ static void sig_handler(int signum) return; } } -#else + +#ifdef WIN32 static void sigwinch_cb(uv_signal_t *handle, int signum) { int width, height; - uv_tty_t out; - uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); - uv_tty_get_winsize(&out, &width, &height); + uv_tty_get_winsize(&tty_out, &width, &height); fprintf(stderr, "rows: %d, cols: %d\n", height, width); - uv_close((uv_handle_t *)&out, NULL); } #endif @@ -92,19 +81,10 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) } int *interrupted = stream->data; -#ifdef _WIN32 - // HACK: Special-case to avoid relying on SIGWINCH on Windows. - // See note at Screen:try_resize(). - bool invoke_sigwinch_handler = false; -#endif for (int i = 0; i < cnt; i++) { if (buf->base[i] == CTRL_C) { (*interrupted)++; -#ifdef _WIN32 - } else if (buf->base[i] == CTRL_Q) { - invoke_sigwinch_handler = true; -#endif } } @@ -113,26 +93,10 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) uv_tty_t out; uv_tty_init(&write_loop, &out, fileno(stdout), 0); -// #ifdef _WIN32 -// if (invoke_sigwinch_handler) { -// int width, height; -// uv_tty_get_winsize(&out, &width, &height); -// if (screen_rect.width != width || screen_rect.height != height) { -// screen_rect.width = width; -// screen_rect.height = height; -// // HACK: Invoke directly. See note at Screen:try_resize(). -// sig_handler(SIGWINCH); -// uv_run(&write_loop, UV_RUN_NOWAIT); -// } -// } else { -// #endif - uv_write_t req; - uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; - uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); - uv_run(&write_loop, UV_RUN_DEFAULT); -// #ifdef _WIN32 -// } -// #endif + uv_write_t req; + uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; + uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL); + uv_run(&write_loop, UV_RUN_DEFAULT); uv_close(STRUCT_CAST(uv_handle_t, &out), NULL); uv_run(&write_loop, UV_RUN_DEFAULT); @@ -189,19 +153,13 @@ int main(int argc, char **argv) uv_prepare_t prepare; uv_prepare_init(uv_default_loop(), &prepare); uv_prepare_start(&prepare, prepare_cb); - // uv_tty_t tty; #ifndef WIN32 uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1); #else uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1); - uv_tty_t out; - uv_tty_init(uv_default_loop(), &out, fileno(stdout), 0); - int width; - int height; - uv_tty_get_winsize(&out, &width, &height); - screen_rect.width = width; - screen_rect.height = height; - uv_close((uv_handle_t *)&out, NULL); + uv_tty_init(uv_default_loop(), &tty_out, fileno(stdout), 0); + int width, height; + uv_tty_get_winsize(&tty_out, &width, &height); #endif uv_tty_set_mode(&tty, UV_TTY_MODE_RAW); tty.data = &interrupted; diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 27c6c54d19..c5199f620e 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -68,7 +68,6 @@ describe('terminal', function() describe('when the screen is resized', function() it('will forward a resize request to the program', function() - if helpers.pending_win32(pending) then return end feed([[:]]) -- Go to cmdline-mode, so cursor is at bottom. screen:try_resize(screen._width - 3, screen._height - 2) screen:expect([[ From dbb404542b3dc165e8186c839b5071f6511f8f8e Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 13 Aug 2017 19:54:48 +0200 Subject: [PATCH 15/16] test/win: place cursor at edge to tickle SIGWINCH --- test/functional/terminal/scrollback_spec.lua | 39 ++++++++++---------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index f804862996..acfa92e2f0 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -57,7 +57,7 @@ describe('terminal scrollback', function() end) end) - describe('with the cursor at the last row', function() + describe('with cursor at last row', function() before_each(function() feed_data({'line1', 'line2', 'line3', 'line4', ''}) screen:expect([[ @@ -138,16 +138,17 @@ describe('terminal scrollback', function() end) - describe('and the height is decreased by 1', function() + describe('and height decreased by 1', function() local function will_hide_top_line() - screen:try_resize(screen._width, screen._height - 1) + feed([[:]]) -- Go to cmdline-mode, so cursor is at bottom. + screen:try_resize(screen._width - 2, screen._height - 1) screen:expect([[ - line2 | - line3 | - line4 | - rows: 5, cols: 30 | - {1: } | - {3:-- TERMINAL --} | + line2 | + line3 | + line4 | + rows: 5, cols: 28 | + {2: } | + :^ | ]]) end @@ -156,23 +157,23 @@ describe('terminal scrollback', function() describe('and then decreased by 2', function() before_each(function() will_hide_top_line() - screen:try_resize(screen._width, screen._height - 2) + screen:try_resize(screen._width - 2, screen._height - 2) end) it('will hide the top 3 lines', function() screen:expect([[ - rows: 5, cols: 30 | - rows: 3, cols: 30 | - {1: } | - {3:-- TERMINAL --} | + rows: 5, cols: 28 | + rows: 3, cols: 26 | + {2: } | + :^ | ]]) eq(8, curbuf('line_count')) - feed('3k') + feed([[3k]]) screen:expect([[ - ^line4 | - rows: 5, cols: 30 | - rows: 3, cols: 30 | - | + ^line4 | + rows: 5, cols: 28 | + rows: 3, cols: 26 | + | ]]) end) end) From 9a6eb71ebaac745198fdc4cec68e51731103c3d9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 16 Aug 2017 01:04:34 +0200 Subject: [PATCH 16/16] test/win: give up on this one --- test/functional/terminal/scrollback_spec.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index acfa92e2f0..af9b414311 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -139,6 +139,7 @@ describe('terminal scrollback', function() describe('and height decreased by 1', function() + if helpers.pending_win32(pending) then return end local function will_hide_top_line() feed([[:]]) -- Go to cmdline-mode, so cursor is at bottom. screen:try_resize(screen._width - 2, screen._height - 1)