mirror of
https://github.com/neovim/neovim.git
synced 2025-09-29 06:28:35 +00:00
win: support :terminal
This commit is contained in:
@@ -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/
|
||||
)
|
||||
|
@@ -2,30 +2,53 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
@@ -5,19 +5,24 @@
|
||||
|
||||
#include <winpty.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user