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<ENTER>" 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.
This commit is contained in:
Ryan Prichard 2016-02-24 02:14:19 -06:00 committed by Justin M. Keyes
parent 30cb66e8ba
commit 7f22a27a10
5 changed files with 229 additions and 7 deletions

View File

@ -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)

10
cmake/FindWinpty.cmake Normal file
View File

@ -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)

View File

@ -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}

View File

@ -0,0 +1,189 @@
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#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);
}
}

View File

@ -1,21 +1,23 @@
#ifndef NVIM_OS_PTY_PROCESS_WIN_H
#define NVIM_OS_PTY_PROCESS_WIN_H
#include <uv.h>
#include <winpty.h>
#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