mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
commit
5b32bce73c
@ -351,6 +351,11 @@ endif()
|
|||||||
find_package(LibVterm REQUIRED)
|
find_package(LibVterm REQUIRED)
|
||||||
include_directories(SYSTEM ${LIBVTERM_INCLUDE_DIRS})
|
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_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_MSAN "Enable Clang memory sanitizer for nvim binary." OFF)
|
||||||
option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF)
|
option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF)
|
||||||
|
10
cmake/FindWinpty.cmake
Normal file
10
cmake/FindWinpty.cmake
Normal 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)
|
@ -111,6 +111,9 @@ foreach(sfile ${NVIM_SOURCES})
|
|||||||
if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
|
if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
|
||||||
list(APPEND to_remove ${sfile})
|
list(APPEND to_remove ${sfile})
|
||||||
endif()
|
endif()
|
||||||
|
if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$")
|
||||||
|
list(APPEND to_remove ${sfile})
|
||||||
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
list(REMOVE_ITEM NVIM_SOURCES ${to_remove})
|
list(REMOVE_ITEM NVIM_SOURCES ${to_remove})
|
||||||
@ -350,6 +353,10 @@ if(Iconv_LIBRARIES)
|
|||||||
list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES})
|
list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES})
|
||||||
endif()
|
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.
|
# Put these last on the link line, since multiple things may depend on them.
|
||||||
list(APPEND NVIM_LINK_LIBRARIES
|
list(APPEND NVIM_LINK_LIBRARIES
|
||||||
${LIBUV_LIBRARIES}
|
${LIBUV_LIBRARIES}
|
||||||
@ -415,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/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/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/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/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/
|
COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/
|
||||||
@ -428,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/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/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/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/
|
COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/platforms/qwindows.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms/
|
||||||
)
|
)
|
||||||
@ -526,6 +535,7 @@ endfunction()
|
|||||||
|
|
||||||
set(NO_SINGLE_CHECK_HEADERS
|
set(NO_SINGLE_CHECK_HEADERS
|
||||||
os/win_defs.h
|
os/win_defs.h
|
||||||
|
os/pty_process_win.h
|
||||||
regexp_defs.h
|
regexp_defs.h
|
||||||
syntax_defs.h
|
syntax_defs.h
|
||||||
terminal.h
|
terminal.h
|
||||||
|
410
src/nvim/os/pty_process_win.c
Normal file
410
src/nvim/os/pty_process_win.c
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <winpty_constants.h>
|
||||||
|
|
||||||
|
#include "nvim/os/os.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)
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns zero on success, or negative error code.
|
||||||
|
int pty_process_spawn(PtyProcess *ptyproc)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
Process *proc = (Process *)ptyproc;
|
||||||
|
int status = 0;
|
||||||
|
winpty_error_ptr_t err = NULL;
|
||||||
|
winpty_config_t *cfg = NULL;
|
||||||
|
winpty_spawn_config_t *spawncfg = NULL;
|
||||||
|
winpty_t *winpty_object = NULL;
|
||||||
|
char *in_name = NULL;
|
||||||
|
char *out_name = NULL;
|
||||||
|
HANDLE process_handle = NULL;
|
||||||
|
uv_connect_t *in_req = NULL;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proc->in != NULL) {
|
||||||
|
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 != NULL) {
|
||||||
|
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);
|
||||||
|
if (status != 0) {
|
||||||
|
emsg = "Failed to convert pwd form utf8 to utf16.";
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
spawncfg = winpty_spawn_config_new(
|
||||||
|
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
|
||||||
|
NULL, // Optional application name
|
||||||
|
cmd_line,
|
||||||
|
cwd,
|
||||||
|
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
|
||||||
|
&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);
|
||||||
|
|
||||||
|
if (!RegisterWaitForSingleObject(
|
||||||
|
&ptyproc->finish_wait,
|
||||||
|
process_handle,
|
||||||
|
pty_process_finish1,
|
||||||
|
ptyproc,
|
||||||
|
INFINITE,
|
||||||
|
WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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->winpty_object = winpty_object;
|
||||||
|
ptyproc->process_handle = process_handle;
|
||||||
|
winpty_object = NULL;
|
||||||
|
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);
|
||||||
|
winpty_free(winpty_object);
|
||||||
|
xfree(in_name);
|
||||||
|
xfree(out_name);
|
||||||
|
if (process_handle != NULL) {
|
||||||
|
CloseHandle(process_handle);
|
||||||
|
}
|
||||||
|
xfree(in_req);
|
||||||
|
xfree(out_req);
|
||||||
|
xfree(cmd_line);
|
||||||
|
xfree(cwd);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pty_process_resize(PtyProcess *ptyproc, uint16_t width,
|
||||||
|
uint16_t height)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
if (ptyproc->winpty_object != NULL) {
|
||||||
|
winpty_set_size(ptyproc->winpty_object, width, height, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pty_process_close(PtyProcess *ptyproc)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
Process *proc = (Process *)ptyproc;
|
||||||
|
|
||||||
|
pty_process_close_master(ptyproc);
|
||||||
|
|
||||||
|
if (proc->internal_close_cb) {
|
||||||
|
proc->internal_close_cb(proc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pty_process_close_master(PtyProcess *ptyproc)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
if (ptyproc->winpty_object != NULL) {
|
||||||
|
winpty_free(ptyproc->winpty_object);
|
||||||
|
ptyproc->winpty_object = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pty_process_teardown(Loop *loop)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pty_process_connect_cb(uv_connect_t *req, int status)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
assert(status == 0);
|
||||||
|
req->handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
PtyProcess *ptyproc = 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
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the command line to pass to CreateProcessW.
|
||||||
|
///
|
||||||
|
/// @param[in] argv Array with string arguments.
|
||||||
|
/// @param[out] cmd_line Location where saved builded cmd line.
|
||||||
|
///
|
||||||
|
/// @returns zero on success, or error code of MultiByteToWideChar function.
|
||||||
|
///
|
||||||
|
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;
|
||||||
|
size_t argc = 0;
|
||||||
|
QUEUE args_q;
|
||||||
|
|
||||||
|
QUEUE_INIT(&args_q);
|
||||||
|
while (*argv) {
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
argc++;
|
||||||
|
argv++;
|
||||||
|
}
|
||||||
|
|
||||||
|
utf8_cmd_line_len += argc;
|
||||||
|
char *utf8_cmd_line = xmalloc(utf8_cmd_line_len);
|
||||||
|
*utf8_cmd_line = NUL;
|
||||||
|
while (1) {
|
||||||
|
QUEUE *head = QUEUE_HEAD(&args_q);
|
||||||
|
QUEUE_REMOVE(head);
|
||||||
|
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(utf8_cmd_line, " ", utf8_cmd_line_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = utf8_to_utf16(utf8_cmd_line, cmd_line);
|
||||||
|
xfree(utf8_cmd_line);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emulate quote_cmd_arg of libuv and quotes command line argument.
|
||||||
|
/// Most of the code came from libuv.
|
||||||
|
///
|
||||||
|
/// @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 *dest, size_t dest_remaining, const char *src)
|
||||||
|
FUNC_ATTR_NONNULL_ALL
|
||||||
|
{
|
||||||
|
size_t src_len = strlen(src);
|
||||||
|
bool quote_hit = true;
|
||||||
|
char *start = dest;
|
||||||
|
|
||||||
|
if (src_len == 0) {
|
||||||
|
// Need double quotation for empty argument.
|
||||||
|
snprintf(dest, dest_remaining, "\"\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NULL == strpbrk(src, " \t\"")) {
|
||||||
|
// No quotation needed.
|
||||||
|
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(dest, dest_remaining, "\"%s\"", src);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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\\"
|
||||||
|
|
||||||
|
assert(dest_remaining--);
|
||||||
|
*(dest++) = NUL;
|
||||||
|
assert(dest_remaining--);
|
||||||
|
*(dest++) = '"';
|
||||||
|
for (size_t i = src_len; i > 0; i--) {
|
||||||
|
assert(dest_remaining--);
|
||||||
|
*(dest++) = src[i - 1];
|
||||||
|
if (quote_hit && src[i - 1] == '\\') {
|
||||||
|
assert(dest_remaining--);
|
||||||
|
*(dest++) = '\\';
|
||||||
|
} else if (src[i - 1] == '"') {
|
||||||
|
quote_hit = true;
|
||||||
|
assert(dest_remaining--);
|
||||||
|
*(dest++) = '\\';
|
||||||
|
} else {
|
||||||
|
quote_hit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(dest_remaining);
|
||||||
|
*dest = '"';
|
||||||
|
|
||||||
|
while (start < dest) {
|
||||||
|
char tmp = *start;
|
||||||
|
*start = *dest;
|
||||||
|
*dest = tmp;
|
||||||
|
start++;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,27 @@
|
|||||||
#ifndef NVIM_OS_PTY_PROCESS_WIN_H
|
#ifndef NVIM_OS_PTY_PROCESS_WIN_H
|
||||||
#define NVIM_OS_PTY_PROCESS_WIN_H
|
#define NVIM_OS_PTY_PROCESS_WIN_H
|
||||||
|
|
||||||
#include "nvim/event/libuv_process.h"
|
#include <uv.h>
|
||||||
|
#include <winpty.h>
|
||||||
|
|
||||||
|
#include "nvim/event/process.h"
|
||||||
|
#include "nvim/lib/queue.h"
|
||||||
|
|
||||||
typedef struct pty_process {
|
typedef struct pty_process {
|
||||||
Process process;
|
Process process;
|
||||||
char *term_name;
|
char *term_name;
|
||||||
uint16_t width, height;
|
uint16_t width, height;
|
||||||
|
winpty_t *winpty_object;
|
||||||
|
HANDLE finish_wait;
|
||||||
|
HANDLE process_handle;
|
||||||
|
uv_timer_t wait_eof_timer;
|
||||||
} PtyProcess;
|
} PtyProcess;
|
||||||
|
|
||||||
#define pty_process_spawn(job) libuv_process_spawn((LibuvProcess *)job)
|
// Structure used by build_cmd_line()
|
||||||
#define pty_process_close(job) libuv_process_close((LibuvProcess *)job)
|
typedef struct arg_node {
|
||||||
#define pty_process_close_master(job) libuv_process_close((LibuvProcess *)job)
|
char *arg; // pointer to argument.
|
||||||
#define pty_process_resize(job, width, height) ( \
|
QUEUE node; // QUEUE structure.
|
||||||
(void)job, (void)width, (void)height, 0)
|
} ArgNode;
|
||||||
#define pty_process_teardown(loop) ((void)loop, 0)
|
|
||||||
|
|
||||||
static inline PtyProcess pty_process_init(Loop *loop, void *data)
|
static inline PtyProcess pty_process_init(Loop *loop, void *data)
|
||||||
{
|
{
|
||||||
@ -23,7 +30,14 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
|
|||||||
rv.term_name = NULL;
|
rv.term_name = NULL;
|
||||||
rv.width = 80;
|
rv.width = 80;
|
||||||
rv.height = 24;
|
rv.height = 24;
|
||||||
|
rv.winpty_object = NULL;
|
||||||
|
rv.finish_wait = NULL;
|
||||||
|
rv.process_handle = NULL;
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "os/pty_process_win.h.generated.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // NVIM_OS_PTY_PROCESS_WIN_H
|
#endif // NVIM_OS_PTY_PROCESS_WIN_H
|
||||||
|
@ -5,42 +5,45 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <uv.h>
|
#include <uv.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
#else
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// -V:STRUCT_CAST:641
|
// -V:STRUCT_CAST:641
|
||||||
#define STRUCT_CAST(Type, obj) ((Type *)(obj))
|
#define STRUCT_CAST(Type, obj) ((Type *)(obj))
|
||||||
|
|
||||||
uv_tty_t tty;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
bool owns_tty(void)
|
|
||||||
{
|
|
||||||
HWND consoleWnd = GetConsoleWindow();
|
|
||||||
DWORD dwProcessId;
|
|
||||||
GetWindowThreadProcessId(consoleWnd, &dwProcessId);
|
|
||||||
return GetCurrentProcessId() == dwProcessId;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
bool owns_tty(void)
|
|
||||||
{
|
|
||||||
return getsid(0) == getpid();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY)
|
#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY)
|
||||||
#define BUF_SIZE 0xfff
|
#define BUF_SIZE 0xfff
|
||||||
|
#define CTRL_C 0x03
|
||||||
|
|
||||||
static void walk_cb(uv_handle_t *handle, void *arg) {
|
uv_tty_t tty;
|
||||||
|
uv_tty_t tty_out;
|
||||||
|
|
||||||
|
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
|
||||||
|
return getsid(0) == getpid();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk_cb(uv_handle_t *handle, void *arg)
|
||||||
|
{
|
||||||
if (!uv_is_closing(handle)) {
|
if (!uv_is_closing(handle)) {
|
||||||
uv_close(handle, NULL);
|
uv_close(handle, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef WIN32
|
|
||||||
static void sig_handler(int signum)
|
static void sig_handler(int signum)
|
||||||
{
|
{
|
||||||
switch(signum) {
|
switch (signum) {
|
||||||
case SIGWINCH: {
|
case SIGWINCH: {
|
||||||
int width, height;
|
int width, height;
|
||||||
uv_tty_get_winsize(&tty, &width, &height);
|
uv_tty_get_winsize(&tty, &width, &height);
|
||||||
@ -54,15 +57,15 @@ static void sig_handler(int signum)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// static void sigwinch_cb(uv_signal_t *handle, int signum)
|
#ifdef WIN32
|
||||||
// {
|
static void sigwinch_cb(uv_signal_t *handle, int signum)
|
||||||
// int width, height;
|
{
|
||||||
// uv_tty_t *tty = handle->data;
|
int width, height;
|
||||||
// uv_tty_get_winsize(tty, &width, &height);
|
uv_tty_get_winsize(&tty_out, &width, &height);
|
||||||
// fprintf(stderr, "rows: %d, cols: %d\n", height, width);
|
fprintf(stderr, "rows: %d, cols: %d\n", height, width);
|
||||||
// }
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
|
static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
|
||||||
{
|
{
|
||||||
@ -80,7 +83,7 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
|
|||||||
int *interrupted = stream->data;
|
int *interrupted = stream->data;
|
||||||
|
|
||||||
for (int i = 0; i < cnt; i++) {
|
for (int i = 0; i < cnt; i++) {
|
||||||
if (buf->base[i] == 3) {
|
if (buf->base[i] == CTRL_C) {
|
||||||
(*interrupted)++;
|
(*interrupted)++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,11 +91,13 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
|
|||||||
uv_loop_t write_loop;
|
uv_loop_t write_loop;
|
||||||
uv_loop_init(&write_loop);
|
uv_loop_init(&write_loop);
|
||||||
uv_tty_t out;
|
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_write_t req;
|
||||||
uv_buf_t b = {.base = buf->base, .len = (size_t)cnt};
|
uv_buf_t b = {.base = buf->base, .len = (size_t)cnt};
|
||||||
uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL);
|
uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL);
|
||||||
uv_run(&write_loop, UV_RUN_DEFAULT);
|
uv_run(&write_loop, UV_RUN_DEFAULT);
|
||||||
|
|
||||||
uv_close(STRUCT_CAST(uv_handle_t, &out), NULL);
|
uv_close(STRUCT_CAST(uv_handle_t, &out), NULL);
|
||||||
uv_run(&write_loop, UV_RUN_DEFAULT);
|
uv_run(&write_loop, UV_RUN_DEFAULT);
|
||||||
if (uv_loop_close(&write_loop)) {
|
if (uv_loop_close(&write_loop)) {
|
||||||
@ -137,7 +142,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
int count = atoi(argv[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);
|
printf("line%d\n", i);
|
||||||
}
|
}
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
@ -148,8 +153,14 @@ int main(int argc, char **argv)
|
|||||||
uv_prepare_t prepare;
|
uv_prepare_t prepare;
|
||||||
uv_prepare_init(uv_default_loop(), &prepare);
|
uv_prepare_init(uv_default_loop(), &prepare);
|
||||||
uv_prepare_start(&prepare, prepare_cb);
|
uv_prepare_start(&prepare, prepare_cb);
|
||||||
// uv_tty_t tty;
|
#ifndef WIN32
|
||||||
uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1);
|
uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1);
|
||||||
|
#else
|
||||||
|
uv_tty_init(uv_default_loop(), &tty, fileno(stdin), 1);
|
||||||
|
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);
|
uv_tty_set_mode(&tty, UV_TTY_MODE_RAW);
|
||||||
tty.data = &interrupted;
|
tty.data = &interrupted;
|
||||||
uv_read_start(STRUCT_CAST(uv_stream_t, &tty), alloc_cb, read_cb);
|
uv_read_start(STRUCT_CAST(uv_stream_t, &tty), alloc_cb, read_cb);
|
||||||
@ -160,15 +171,17 @@ int main(int argc, char **argv)
|
|||||||
sa.sa_handler = sig_handler;
|
sa.sa_handler = sig_handler;
|
||||||
sigaction(SIGHUP, &sa, NULL);
|
sigaction(SIGHUP, &sa, NULL);
|
||||||
sigaction(SIGWINCH, &sa, NULL);
|
sigaction(SIGWINCH, &sa, NULL);
|
||||||
// uv_signal_t sigwinch_watcher;
|
#else
|
||||||
// uv_signal_init(uv_default_loop(), &sigwinch_watcher);
|
uv_signal_t sigwinch_watcher;
|
||||||
// sigwinch_watcher.data = &tty;
|
uv_signal_init(uv_default_loop(), &sigwinch_watcher);
|
||||||
// uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH);
|
uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH);
|
||||||
#endif
|
#endif
|
||||||
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
// XXX: Without this the SIGHUP handler is skipped on some systems.
|
// XXX: Without this the SIGHUP handler is skipped on some systems.
|
||||||
sleep(100);
|
sleep(100);
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.s
|
|||||||
local eq, neq = helpers.eq, helpers.neq
|
local eq, neq = helpers.eq, helpers.neq
|
||||||
local write_file = helpers.write_file
|
local write_file = helpers.write_file
|
||||||
|
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe('terminal buffer', function()
|
describe('terminal buffer', function()
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
@ -160,6 +158,7 @@ describe('terminal buffer', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('handles loss of focus gracefully', function()
|
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.
|
-- Change the statusline to avoid printing the file name, which varies.
|
||||||
nvim('set_option', 'statusline', '==========')
|
nvim('set_option', 'statusline', '==========')
|
||||||
feed_command('set laststatus=0')
|
feed_command('set laststatus=0')
|
||||||
@ -205,7 +204,6 @@ describe('terminal buffer', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe('No heap-buffer-overflow when using', function()
|
describe('No heap-buffer-overflow when using', function()
|
||||||
|
|
||||||
local testfilename = 'Xtestfile-functional-terminal-buffers_spec'
|
local testfilename = 'Xtestfile-functional-terminal-buffers_spec'
|
||||||
|
|
||||||
before_each(function()
|
before_each(function()
|
||||||
|
@ -7,8 +7,6 @@ local feed_command = helpers.feed_command
|
|||||||
local hide_cursor = thelpers.hide_cursor
|
local hide_cursor = thelpers.hide_cursor
|
||||||
local show_cursor = thelpers.show_cursor
|
local show_cursor = thelpers.show_cursor
|
||||||
|
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe('terminal cursor', function()
|
describe('terminal cursor', function()
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ local Screen = require('test.functional.ui.screen')
|
|||||||
local clear, wait, nvim = helpers.clear, helpers.wait, helpers.nvim
|
local clear, wait, nvim = helpers.clear, helpers.wait, helpers.nvim
|
||||||
local nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq
|
local nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq
|
||||||
local feed_command, eval = helpers.feed_command, helpers.eval
|
local feed_command, eval = helpers.feed_command, helpers.eval
|
||||||
|
local iswin = helpers.iswin
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe(':terminal', function()
|
describe(':terminal', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
before_each(function()
|
before_each(function()
|
||||||
@ -174,12 +174,15 @@ describe(':terminal (with fake shell)', function()
|
|||||||
eq('term://', string.match(eval('bufname("%")'), "^term://"))
|
eq('term://', string.match(eval('bufname("%")'), "^term://"))
|
||||||
helpers.feed([[<C-\><C-N>]])
|
helpers.feed([[<C-\><C-N>]])
|
||||||
feed_command([[find */shadacat.py]])
|
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)
|
end)
|
||||||
|
|
||||||
it('works with gf', function()
|
it('works with gf', function()
|
||||||
terminal_with_fake_shell([[echo "scripts/shadacat.py"]])
|
terminal_with_fake_shell([[echo "scripts/shadacat.py"]])
|
||||||
wait()
|
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
ready $ echo "scripts/shadacat.py" |
|
ready $ echo "scripts/shadacat.py" |
|
||||||
|
|
|
|
||||||
|
@ -33,7 +33,6 @@ local function disable_mouse() feed_termcode('[?1002l') end
|
|||||||
|
|
||||||
local default_command = '["'..nvim_dir..'/tty-test'..'"]'
|
local default_command = '["'..nvim_dir..'/tty-test'..'"]'
|
||||||
|
|
||||||
|
|
||||||
local function screen_setup(extra_rows, command, cols)
|
local function screen_setup(extra_rows, command, cols)
|
||||||
extra_rows = extra_rows and extra_rows or 0
|
extra_rows = extra_rows and extra_rows or 0
|
||||||
command = command and command or default_command
|
command = command and command or default_command
|
||||||
|
@ -5,8 +5,6 @@ local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim
|
|||||||
local nvim_dir, command = helpers.nvim_dir, helpers.command
|
local nvim_dir, command = helpers.nvim_dir, helpers.command
|
||||||
local eq, eval = helpers.eq, helpers.eval
|
local eq, eval = helpers.eq, helpers.eval
|
||||||
|
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe('terminal window highlighting', function()
|
describe('terminal window highlighting', function()
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
@ -55,6 +53,7 @@ describe('terminal window highlighting', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
local function pass_attrs()
|
local function pass_attrs()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
screen:expect(sub([[
|
screen:expect(sub([[
|
||||||
tty ready |
|
tty ready |
|
||||||
{NUM:text}text{10: } |
|
{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', pass_attrs)
|
||||||
|
|
||||||
it('will pass the corresponding attributes on scrollback', function()
|
it('will pass the corresponding attributes on scrollback', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
pass_attrs()
|
pass_attrs()
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for i = 1, 8 do
|
for i = 1, 8 do
|
||||||
@ -145,6 +145,7 @@ describe('terminal window highlighting with custom palette', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('will use the custom color', function()
|
it('will use the custom color', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
thelpers.set_fg(3)
|
thelpers.set_fg(3)
|
||||||
thelpers.feed_data('text')
|
thelpers.feed_data('text')
|
||||||
thelpers.clear_attrs()
|
thelpers.clear_attrs()
|
||||||
|
@ -4,8 +4,6 @@ local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval
|
|||||||
local feed, nvim = helpers.feed, helpers.nvim
|
local feed, nvim = helpers.feed, helpers.nvim
|
||||||
local feed_data = thelpers.feed_data
|
local feed_data = thelpers.feed_data
|
||||||
|
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe('terminal mouse', function()
|
describe('terminal mouse', function()
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
@ -67,6 +65,7 @@ describe('terminal mouse', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('will forward mouse clicks to the program', function()
|
it('will forward mouse clicks to the program', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
feed('<LeftMouse><1,2>')
|
feed('<LeftMouse><1,2>')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
line27 |
|
line27 |
|
||||||
@ -80,6 +79,7 @@ describe('terminal mouse', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('will forward mouse scroll to the program', function()
|
it('will forward mouse scroll to the program', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
feed('<ScrollWheelUp><0,0>')
|
feed('<ScrollWheelUp><0,0>')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
line27 |
|
line27 |
|
||||||
@ -94,6 +94,7 @@ describe('terminal mouse', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe('with a split window and other buffer', function()
|
describe('with a split window and other buffer', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
before_each(function()
|
before_each(function()
|
||||||
feed('<c-\\><c-n>:vsp<cr>')
|
feed('<c-\\><c-n>:vsp<cr>')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
|
@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each)
|
|||||||
local thelpers = require('test.functional.terminal.helpers')
|
local thelpers = require('test.functional.terminal.helpers')
|
||||||
local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf
|
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 feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.feed_command
|
||||||
|
local iswin = helpers.iswin
|
||||||
local eval = helpers.eval
|
local eval = helpers.eval
|
||||||
local command = helpers.command
|
local command = helpers.command
|
||||||
local wait = helpers.wait
|
local wait = helpers.wait
|
||||||
@ -11,8 +12,6 @@ local curbufmeths = helpers.curbufmeths
|
|||||||
local nvim = helpers.nvim
|
local nvim = helpers.nvim
|
||||||
local feed_data = thelpers.feed_data
|
local feed_data = thelpers.feed_data
|
||||||
|
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe('terminal scrollback', function()
|
describe('terminal scrollback', function()
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ describe('terminal scrollback', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('with the cursor at the last row', function()
|
describe('with cursor at last row', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
feed_data({'line1', 'line2', 'line3', 'line4', ''})
|
feed_data({'line1', 'line2', 'line3', 'line4', ''})
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
@ -139,16 +138,18 @@ describe('terminal scrollback', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
describe('and the height is decreased by 1', function()
|
describe('and height decreased by 1', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
local function will_hide_top_line()
|
local function will_hide_top_line()
|
||||||
screen:try_resize(screen._width, screen._height - 1)
|
feed([[<C-\><C-N>:]]) -- Go to cmdline-mode, so cursor is at bottom.
|
||||||
|
screen:try_resize(screen._width - 2, screen._height - 1)
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
line2 |
|
line2 |
|
||||||
line3 |
|
line3 |
|
||||||
line4 |
|
line4 |
|
||||||
rows: 5, cols: 30 |
|
rows: 5, cols: 28 |
|
||||||
{1: } |
|
{2: } |
|
||||||
{3:-- TERMINAL --} |
|
:^ |
|
||||||
]])
|
]])
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -157,23 +158,23 @@ describe('terminal scrollback', function()
|
|||||||
describe('and then decreased by 2', function()
|
describe('and then decreased by 2', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
will_hide_top_line()
|
will_hide_top_line()
|
||||||
screen:try_resize(screen._width, screen._height - 2)
|
screen:try_resize(screen._width - 2, screen._height - 2)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('will hide the top 3 lines', function()
|
it('will hide the top 3 lines', function()
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
rows: 5, cols: 30 |
|
rows: 5, cols: 28 |
|
||||||
rows: 3, cols: 30 |
|
rows: 3, cols: 26 |
|
||||||
{1: } |
|
{2: } |
|
||||||
{3:-- TERMINAL --} |
|
:^ |
|
||||||
]])
|
]])
|
||||||
eq(8, curbuf('line_count'))
|
eq(8, curbuf('line_count'))
|
||||||
feed('<c-\\><c-n>3k')
|
feed([[<C-\><C-N>3k]])
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
^line4 |
|
^line4 |
|
||||||
rows: 5, cols: 30 |
|
rows: 5, cols: 28 |
|
||||||
rows: 3, cols: 30 |
|
rows: 3, cols: 26 |
|
||||||
|
|
|
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
@ -181,6 +182,11 @@ describe('terminal scrollback', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe('with empty lines after the cursor', function()
|
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()
|
describe('and the height is decreased by 2', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
screen:try_resize(screen._width, screen._height - 2)
|
screen:try_resize(screen._width, screen._height - 2)
|
||||||
@ -255,6 +261,10 @@ describe('terminal scrollback', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe('and the height is increased by 1', function()
|
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()
|
local function pop_then_push()
|
||||||
screen:try_resize(screen._width, screen._height + 1)
|
screen:try_resize(screen._width, screen._height + 1)
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
@ -384,10 +394,20 @@ describe("'scrollback' option", function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
it('set to 0 behaves as 1', function()
|
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)
|
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)
|
screen:expect('line30 ', nil, nil, nil, true)
|
||||||
retry(nil, nil, function() expect_lines(7) end)
|
retry(nil, nil, function() expect_lines(7) end)
|
||||||
|
|
||||||
@ -395,7 +415,13 @@ describe("'scrollback' option", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('deletes lines (only) if necessary', function()
|
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)
|
curbufmeths.set_option('scrollback', 200)
|
||||||
|
|
||||||
@ -403,7 +429,11 @@ describe("'scrollback' option", function()
|
|||||||
screen:expect('$', nil, nil, nil, true)
|
screen:expect('$', nil, nil, nil, true)
|
||||||
|
|
||||||
wait()
|
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)
|
screen:expect('line30 ', nil, nil, nil, true)
|
||||||
|
|
||||||
@ -416,7 +446,11 @@ describe("'scrollback' option", function()
|
|||||||
-- Terminal job data is received asynchronously, may happen before the
|
-- Terminal job data is received asynchronously, may happen before the
|
||||||
-- 'scrollback' option is synchronized with the internal sb_buffer.
|
-- 'scrollback' option is synchronized with the internal sb_buffer.
|
||||||
command('sleep 100m')
|
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)
|
screen:expect('line40 ', nil, nil, nil, true)
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@ local thelpers = require('test.functional.terminal.helpers')
|
|||||||
local feed, clear = helpers.feed, helpers.clear
|
local feed, clear = helpers.feed, helpers.clear
|
||||||
local wait = helpers.wait
|
local wait = helpers.wait
|
||||||
|
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe('terminal window', function()
|
describe('terminal window', function()
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
|
@ -4,8 +4,6 @@ local clear = helpers.clear
|
|||||||
local feed, nvim = helpers.feed, helpers.nvim
|
local feed, nvim = helpers.feed, helpers.nvim
|
||||||
local feed_command = helpers.feed_command
|
local feed_command = helpers.feed_command
|
||||||
|
|
||||||
if helpers.pending_win32(pending) then return end
|
|
||||||
|
|
||||||
describe('terminal', function()
|
describe('terminal', function()
|
||||||
local screen
|
local screen
|
||||||
|
|
||||||
@ -25,6 +23,7 @@ describe('terminal', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('resets its size when entering terminal window', function()
|
it('resets its size when entering terminal window', function()
|
||||||
|
if helpers.pending_win32(pending) then return end
|
||||||
feed('<c-\\><c-n>')
|
feed('<c-\\><c-n>')
|
||||||
feed_command('2split')
|
feed_command('2split')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
@ -69,31 +68,25 @@ describe('terminal', function()
|
|||||||
|
|
||||||
describe('when the screen is resized', function()
|
describe('when the screen is resized', function()
|
||||||
it('will forward a resize request to the program', function()
|
it('will forward a resize request to the program', function()
|
||||||
screen:try_resize(screen._width + 3, screen._height + 5)
|
feed([[<C-\><C-N>:]]) -- Go to cmdline-mode, so cursor is at bottom.
|
||||||
screen:expect([[
|
screen:try_resize(screen._width - 3, screen._height - 2)
|
||||||
tty ready |
|
|
||||||
rows: 14, cols: 53 |
|
|
||||||
{1: } |
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
{3:-- TERMINAL --} |
|
|
||||||
]])
|
|
||||||
screen:try_resize(screen._width - 6, screen._height - 10)
|
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
tty ready |
|
tty ready |
|
||||||
rows: 14, cols: 53 |
|
rows: 7, cols: 47 |
|
||||||
rows: 4, cols: 47 |
|
{2: } |
|
||||||
{1: } |
|
|
|
||||||
{3:-- TERMINAL --} |
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
:^ |
|
||||||
|
]])
|
||||||
|
screen:try_resize(screen._width - 6, screen._height - 3)
|
||||||
|
screen:expect([[
|
||||||
|
tty ready |
|
||||||
|
rows: 7, cols: 47 |
|
||||||
|
rows: 4, cols: 41 |
|
||||||
|
{2: } |
|
||||||
|
:^ |
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user