diff --git a/deps/uv/common.gypi b/deps/uv/common.gypi index 9a0be4dc39..373c3aa980 100644 --- a/deps/uv/common.gypi +++ b/deps/uv/common.gypi @@ -32,6 +32,11 @@ 'LinkIncremental': 2, # enable incremental linking }, }, + 'conditions': [ + ['OS != "win"', { + 'defines': [ 'EV_VERIFY=2' ], + }], + ] }, 'Release': { 'defines': [ 'NDEBUG' ], diff --git a/deps/uv/include/uv-private/uv-win.h b/deps/uv/include/uv-private/uv-win.h index f203d67289..372d65c67e 100644 --- a/deps/uv/include/uv-private/uv-win.h +++ b/deps/uv/include/uv-private/uv-win.h @@ -117,6 +117,7 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); HANDLE pipeHandle; \ struct uv_pipe_accept_s* next_pending; \ } uv_pipe_accept_t; \ + \ typedef struct uv_tcp_accept_s { \ UV_REQ_FIELDS \ SOCKET accept_socket; \ @@ -181,6 +182,31 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); struct { uv_pipe_connection_fields }; \ }; +/* TODO: put the parser states in an union - TTY handles are always */ +/* half-duplex so read-state can safely overlap write-state. */ +#define UV_TTY_PRIVATE_FIELDS \ + HANDLE handle; \ + HANDLE read_line_handle; \ + uv_buf_t read_line_buffer; \ + HANDLE read_raw_wait; \ + DWORD original_console_mode; \ + /* Fields used for translating win */ \ + /* keystrokes into vt100 characters */ \ + char last_key[8]; \ + unsigned char last_key_offset; \ + unsigned char last_key_len; \ + INPUT_RECORD last_input_record; \ + WCHAR last_utf16_high_surrogate; \ + /* utf8-to-utf16 conversion state */ \ + unsigned char utf8_bytes_left; \ + unsigned int utf8_codepoint; \ + /* eol conversion state */ \ + unsigned char previous_eol; \ + /* ansi parser state */ \ + unsigned char ansi_parser_state; \ + unsigned char ansi_csi_argc; \ + unsigned short ansi_csi_argv[4]; + #define UV_TIMER_PRIVATE_FIELDS \ RB_ENTRY(uv_timer_s) tree_entry; \ int64_t due; \ @@ -273,8 +299,6 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); int is_path_dir; \ char* buffer; -#define UV_TTY_PRIVATE_FIELDS /* empty */ - int uv_utf16_to_utf8(const wchar_t* utf16Buffer, size_t utf16Size, char* utf8Buffer, size_t utf8Size); int uv_utf8_to_utf16(const char* utf8Buffer, wchar_t* utf16Buffer, diff --git a/deps/uv/src/unix/cares.c b/deps/uv/src/unix/cares.c index a2466f59e6..b2bc16f4c7 100644 --- a/deps/uv/src/unix/cares.c +++ b/deps/uv/src/unix/cares.c @@ -67,6 +67,7 @@ static uv_ares_task_t* uv__ares_task_create(int fd) { if (h == NULL) { uv_fatal_error(ENOMEM, "malloc"); + return NULL; } h->sock = fd; diff --git a/deps/uv/src/unix/fs.c b/deps/uv/src/unix/fs.c index ec24e8b3fa..f1974ca2d6 100644 --- a/deps/uv/src/unix/fs.c +++ b/deps/uv/src/unix/fs.c @@ -149,19 +149,20 @@ static int uv__fs_after(eio_req* eio) { case UV_FS_READLINK: if (req->result == -1) { req->ptr = NULL; - } else { - assert(req->result > 0); - - if ((name = realloc(req->eio->ptr2, req->result + 1)) == NULL) { - /* Not enough memory. Reuse buffer, chop off last byte. */ - name = req->eio->ptr2; - req->result--; - } + break; + } + assert(req->result > 0); + /* Make zero-terminated copy of req->eio->ptr2 */ + if ((req->ptr = name = malloc(req->result + 1))) { + memcpy(name, req->eio->ptr2, req->result); name[req->result] = '\0'; - req->ptr = name; req->result = 0; } + else { + req->errorno = ENOMEM; + req->result = -1; + } break; default: diff --git a/deps/uv/src/unix/udp.c b/deps/uv/src/unix/udp.c index 20e5f3798b..bbd645f514 100644 --- a/deps/uv/src/unix/udp.c +++ b/deps/uv/src/unix/udp.c @@ -301,6 +301,7 @@ static int uv__udp_bind(uv_udp_t* handle, saved_errno = errno; status = -1; + fd = -1; /* Check for bad flags. */ if (flags & ~UV_UDP_IPV6ONLY) { diff --git a/deps/uv/src/win/core.c b/deps/uv/src/win/core.c index 3211bbf2af..cb4a28b823 100644 --- a/deps/uv/src/win/core.c +++ b/deps/uv/src/win/core.c @@ -48,6 +48,9 @@ static void uv_init(void) { /* Initialize FS */ uv_fs_init(); + + /* Initialize console */ + uv_console_init(); } diff --git a/deps/uv/src/win/handle.c b/deps/uv/src/win/handle.c index ab4f64bc5a..b67139cbc6 100644 --- a/deps/uv/src/win/handle.c +++ b/deps/uv/src/win/handle.c @@ -20,11 +20,36 @@ */ #include +#include #include "uv.h" #include "internal.h" +uv_handle_type uv_guess_handle(uv_file file) { + HANDLE handle = (HANDLE) _get_osfhandle(file); + DWORD mode; + + switch (GetFileType(handle)) { + case FILE_TYPE_CHAR: + if (GetConsoleMode(handle, &mode)) { + return UV_TTY; + } else { + return UV_UNKNOWN_HANDLE; + } + + case FILE_TYPE_PIPE: + return UV_NAMED_PIPE; + + case FILE_TYPE_DISK: + return UV_FILE; + + default: + return UV_UNKNOWN_HANDLE; + } +} + + int uv_is_active(uv_handle_t* handle) { switch (handle->type) { case UV_TIMER: @@ -80,6 +105,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) { } return; + case UV_TTY: + uv_tty_close((uv_tty_t*) handle); + return; + case UV_UDP: udp = (uv_udp_t*) handle; uv_udp_recv_stop(udp); @@ -159,6 +188,10 @@ void uv_process_endgames(uv_loop_t* loop) { uv_pipe_endgame(loop, (uv_pipe_t*) handle); break; + case UV_TTY: + uv_tty_endgame(loop, (uv_tty_t*) handle); + break; + case UV_UDP: uv_udp_endgame(loop, (uv_udp_t*) handle); break; diff --git a/deps/uv/src/win/internal.h b/deps/uv/src/win/internal.h index 87a64eda1b..fa20e4660c 100644 --- a/deps/uv/src/win/internal.h +++ b/deps/uv/src/win/internal.h @@ -64,6 +64,7 @@ void uv_process_timers(uv_loop_t* loop); #define UV_HANDLE_UV_ALLOCED 0x20000 #define UV_HANDLE_SYNC_BYPASS_IOCP 0x40000 #define UV_HANDLE_ZERO_READ 0x80000 +#define UV_HANDLE_TTY_RAW 0x100000 void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle); void uv_process_endgames(uv_loop_t* loop); @@ -173,6 +174,33 @@ void uv_process_pipe_connect_req(uv_loop_t* loop, uv_pipe_t* handle, void uv_process_pipe_shutdown_req(uv_loop_t* loop, uv_pipe_t* handle, uv_shutdown_t* req); + +/* + * TTY + */ +void uv_console_init(); + +int uv_tty_read_start(uv_tty_t* handle, uv_alloc_cb alloc_cb, + uv_read_cb read_cb); +int uv_tty_read_stop(uv_tty_t* handle); +int uv_tty_write(uv_loop_t* loop, uv_write_t* req, uv_tty_t* handle, + uv_buf_t bufs[], int bufcnt, uv_write_cb cb); +void uv_tty_close(uv_tty_t* handle); + +void uv_process_tty_read_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req); +void uv_process_tty_write_req(uv_loop_t* loop, uv_tty_t* handle, + uv_write_t* req); +/* TODO: remove me */ +void uv_process_tty_accept_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* raw_req); +/* TODO: remove me */ +void uv_process_tty_connect_req(uv_loop_t* loop, uv_tty_t* handle, + uv_connect_t* req); + +void uv_tty_endgame(uv_loop_t* loop, uv_tty_t* handle); + + /* * Loop watchers */ diff --git a/deps/uv/src/win/pipe.c b/deps/uv/src/win/pipe.c index 7832c6a41a..8d7891539a 100644 --- a/deps/uv/src/win/pipe.c +++ b/deps/uv/src/win/pipe.c @@ -231,6 +231,7 @@ void uv_pipe_endgame(uv_loop_t* loop, uv_pipe_t* handle) { if (result) { /* Mark the handle as shut now to avoid going through this again. */ handle->flags |= UV_HANDLE_SHUT; + return; } else { /* Failure. */ @@ -678,6 +679,8 @@ static void uv_pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) { /* Make this req pending reporting an error. */ SET_REQ_ERROR(req, WSAGetLastError()); uv_insert_pending_req(loop, req); + + handle->flags |= UV_HANDLE_READ_PENDING; handle->reqs_pending++; return; } diff --git a/deps/uv/src/win/req.c b/deps/uv/src/win/req.c index a0a6e03dc6..dd2b38809e 100644 --- a/deps/uv/src/win/req.c +++ b/deps/uv/src/win/req.c @@ -87,6 +87,12 @@ static uv_req_t* uv_remove_pending_req(uv_loop_t* loop) { req); \ break; \ \ + case UV_TTY: \ + uv_process_tty_##method##_req(loop, \ + (uv_tty_t*) ((req)->handle_at), \ + req); \ + break; \ + \ default: \ assert(0); \ } \ diff --git a/deps/uv/src/win/stream.c b/deps/uv/src/win/stream.c index 26964110fc..476496759f 100644 --- a/deps/uv/src/win/stream.c +++ b/deps/uv/src/win/stream.c @@ -83,6 +83,8 @@ int uv_read_start(uv_stream_t* handle, uv_alloc_cb alloc_cb, return uv_tcp_read_start((uv_tcp_t*)handle, alloc_cb, read_cb); case UV_NAMED_PIPE: return uv_pipe_read_start((uv_pipe_t*)handle, alloc_cb, read_cb); + case UV_TTY: + return uv_tty_read_start((uv_tty_t*) handle, alloc_cb, read_cb); default: assert(0); return -1; @@ -91,9 +93,12 @@ int uv_read_start(uv_stream_t* handle, uv_alloc_cb alloc_cb, int uv_read_stop(uv_stream_t* handle) { - handle->flags &= ~UV_HANDLE_READING; - - return 0; + if (handle->type = UV_TTY) { + return uv_tty_read_stop((uv_tty_t*) handle); + } else { + handle->flags &= ~UV_HANDLE_READING; + return 0; + } } @@ -106,6 +111,8 @@ int uv_write(uv_write_t* req, uv_stream_t* handle, uv_buf_t bufs[], int bufcnt, return uv_tcp_write(loop, req, (uv_tcp_t*) handle, bufs, bufcnt, cb); case UV_NAMED_PIPE: return uv_pipe_write(loop, req, (uv_pipe_t*) handle, bufs, bufcnt, cb); + case UV_TTY: + return uv_tty_write(loop, req, (uv_tty_t*) handle, bufs, bufcnt, cb); default: assert(0); uv_set_sys_error(loop, WSAEINVAL); diff --git a/deps/uv/src/win/tcp.c b/deps/uv/src/win/tcp.c index ebd8353498..783d5fb9a2 100644 --- a/deps/uv/src/win/tcp.c +++ b/deps/uv/src/win/tcp.c @@ -128,7 +128,9 @@ void uv_tcp_endgame(uv_loop_t* loop, uv_tcp_t* handle) { } handle->shutdown_req->cb(handle->shutdown_req, status); } - handle->reqs_pending--; + + DECREASE_PENDING_REQ_COUNT(handle); + return; } if (handle->flags & UV_HANDLE_CLOSING && diff --git a/deps/uv/src/win/tty.c b/deps/uv/src/win/tty.c index a4a4682964..1a4bf11a33 100644 --- a/deps/uv/src/win/tty.c +++ b/deps/uv/src/win/tty.c @@ -19,43 +19,1573 @@ * IN THE SOFTWARE. */ +#include +#include +#include +#include + #include "uv.h" +#include "../uv-common.h" #include "internal.h" -#include + +#define UNICODE_REPLACEMENT_CHARACTER (0xfffd) + +#define ANSI_NORMAL 0x00 +#define ANSI_ESCAPE_SEEN 0x02 +#define ANSI_CSI 0x04 +#define ANSI_ST_CONTROL 0x08 +#define ANSI_IGNORE 0x10 +#define ANSI_IN_ARG 0x20 +#define ANSI_IN_STRING 0x40 +#define ANSI_BACKSLASH_SEEN 0x80 + + +static void uv_tty_update_virtual_window(CONSOLE_SCREEN_BUFFER_INFO* info); + + +/* Null uv_buf_t */ +static const uv_buf_t uv_null_buf_ = { 0, NULL }; + + +/* + * The console virtual window. + * + * Normally cursor movement in windows is relative to the console screen buffer, + * e.g. the application is allowed to overwrite the 'history'. This is very + * inconvenient, it makes absolute cursor movement pretty useless. There is + * also the concept of 'client rect' which is defined by the actual size of + * the console window and the scroll position of the screen buffer, but it's + * very volatile because it changes when the user scrolls. + * + * To make cursor movement behave sensibly we define a virtual window to which + * cursor movement is confined. The virtual window is always as wide as the + * console screen buffer, but it's height is defined by the size of the + * console window. The top of the virtual window aligns with the position + * of the caret when the first stdout/err handle is created, unless that would + * mean that it would extend beyond the bottom of the screen buffer - in that + * that case it's located as far down as possible. + * + * When the user writes a long text or many newlines, such that the output + * reaches beyond the bottom of the virtual window, the virtual window is + * shifted downwards, but not resized. + * + * Since all tty i/o happens on the same console, this window is shared + * between all stdout/stderr handles. + */ + +static int uv_tty_virtual_offset = -1; +static int uv_tty_virtual_height = -1; +static int uv_tty_virtual_width = -1; + +static CRITICAL_SECTION uv_tty_output_lock; + + +void uv_console_init() { + InitializeCriticalSection(&uv_tty_output_lock); +} int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd) { - assert(0 && "implement me"); - return -1; + HANDLE win_handle; + CONSOLE_SCREEN_BUFFER_INFO info; + + win_handle = (HANDLE) _get_osfhandle(fd); + if (win_handle == INVALID_HANDLE_VALUE) { + uv_set_sys_error(loop, ERROR_INVALID_HANDLE); + return -1; + } + + if (!GetConsoleMode(win_handle, &tty->original_console_mode)) { + uv_set_sys_error(loop, GetLastError()); + return -1; + } + + /* Initialize virtual window size; if it fails, assume that this is stdin. */ + if (GetConsoleScreenBufferInfo(win_handle, &info)) { + EnterCriticalSection(&uv_tty_output_lock); + uv_tty_update_virtual_window(&info); + LeaveCriticalSection(&uv_tty_output_lock); + } + + uv_stream_init(loop, (uv_stream_t*) tty); + uv_connection_init((uv_stream_t*) tty); + + tty->type = UV_TTY; + tty->handle = win_handle; + tty->read_line_handle = NULL; + tty->read_line_buffer = uv_null_buf_; + tty->read_raw_wait = NULL; + tty->reqs_pending = 0; + tty->flags |= UV_HANDLE_BOUND; + + /* Init keycode-to-vt100 mapper state. */ + tty->last_key_len = 0; + tty->last_key_offset = 0; + tty->last_utf16_high_surrogate = 0; + memset(&tty->last_input_record, 0, sizeof tty->last_input_record); + + /* Init utf8-to-utf16 conversion state. */ + tty->utf8_bytes_left = 0; + tty->utf8_codepoint = 0; + + /* Initialize eol conversion state */ + tty->previous_eol = 0; + + /* Init ANSI parser state. */ + tty->ansi_parser_state = ANSI_NORMAL; + + return 0; } int uv_tty_set_mode(uv_tty_t* tty, int mode) { - assert(0 && "implement me"); - return -1; + DWORD flags = 0; + unsigned char was_reading; + uv_alloc_cb alloc_cb; + uv_read_cb read_cb; + + if (!!mode == !!(tty->flags & UV_HANDLE_TTY_RAW)) { + return 0; + } + + if (tty->original_console_mode & ENABLE_QUICK_EDIT_MODE) { + flags = ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS; + } + + if (mode) { + /* Raw input */ + flags |= ENABLE_WINDOW_INPUT; + } else { + /* Line-buffered mode. */ + flags |= ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | + ENABLE_EXTENDED_FLAGS | ENABLE_PROCESSED_INPUT; + } + + if (!SetConsoleMode(tty->handle, flags)) { + uv_set_sys_error(tty->loop, GetLastError()); + return -1; + } + + /* If currently reading, stop, and restart reading. */ + if (tty->flags & UV_HANDLE_READING) { + was_reading = 1; + alloc_cb = tty->alloc_cb; + read_cb = tty->read_cb; + + if (was_reading && uv_tty_read_stop(tty) != 0) { + return -1; + } + } else { + was_reading = 0; + } + + /* Update flag. */ + tty->flags &= ~UV_HANDLE_TTY_RAW; + tty->flags |= mode ? UV_HANDLE_TTY_RAW : 0; + + /* If we just stopped reading, restart. */ + if (was_reading && uv_tty_read_start(tty, alloc_cb, read_cb) != 0) { + return -1; + } + + return 0; } int uv_is_tty(uv_file file) { + DWORD result; + return GetConsoleMode((HANDLE) _get_osfhandle(file), &result) != 0; } int uv_tty_get_winsize(uv_tty_t* tty, int* width, int* height) { - assert(0 && "implement me"); - return -1; + CONSOLE_SCREEN_BUFFER_INFO info; + + if (!GetConsoleScreenBufferInfo(tty->handle, &info)) { + uv_set_sys_error(tty->loop, GetLastError()); + return -1; + } + + EnterCriticalSection(&uv_tty_output_lock); + uv_tty_update_virtual_window(&info); + LeaveCriticalSection(&uv_tty_output_lock); + + *width = uv_tty_virtual_width; + *height = uv_tty_virtual_height; + + return 0; } -uv_handle_type uv_guess_handle(uv_file file) { - DWORD result; - int r = GetConsoleMode((HANDLE)_get_osfhandle(file), &result); +static void CALLBACK uv_tty_post_raw_read(void* data, BOOLEAN didTimeout) { + uv_loop_t* loop; + uv_tty_t* handle; + uv_req_t* req; + + assert(data); + assert(!didTimeout); + + req = (uv_req_t*) data; + handle = (uv_tty_t*) req->data; + loop = handle->loop; + + UnregisterWait(handle->read_raw_wait); + handle->read_raw_wait = NULL; + + SET_REQ_SUCCESS(req); + POST_COMPLETION_FOR_REQ(loop, req); +} + + +static void uv_tty_queue_read_raw(uv_loop_t* loop, uv_tty_t* handle) { + uv_req_t* req; + BOOL r; + + assert(handle->flags & UV_HANDLE_READING); + assert(!(handle->flags & UV_HANDLE_READ_PENDING)); + + assert(handle->handle && handle->handle != INVALID_HANDLE_VALUE); + + handle->read_line_buffer = uv_null_buf_; + + req = &handle->read_req; + memset(&req->overlapped, 0, sizeof(req->overlapped)); + + r = RegisterWaitForSingleObject(&handle->read_raw_wait, + handle->handle, + uv_tty_post_raw_read, + (void*) req, + INFINITE, + WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE); + if (!r) { + handle->read_raw_wait = NULL; + SET_REQ_ERROR(req, GetLastError()); + uv_insert_pending_req(loop, req); + } + + handle->flags |= UV_HANDLE_READ_PENDING; + handle->reqs_pending++; +} + + +static DWORD CALLBACK uv_tty_line_read_thread(void* data) { + uv_loop_t* loop; + uv_tty_t* handle; + uv_req_t* req; + DWORD bytes, read_bytes; + + assert(data); + + req = (uv_req_t*) data; + handle = (uv_tty_t*) req->data; + loop = handle->loop; + + assert(handle->read_line_buffer.base != NULL); + assert(handle->read_line_buffer.len > 0); + + /* ReadConsole can't handle big buffers. */ + if (handle->read_line_buffer.len < 8192) { + bytes = handle->read_line_buffer.len; + } else { + bytes = 8192; + } + + /* Todo: Unicode */ + if (ReadConsoleA(handle->read_line_handle, + (void*) handle->read_line_buffer.base, + bytes, + &read_bytes, + NULL)) { + SET_REQ_SUCCESS(req); + req->overlapped.InternalHigh = read_bytes; + } else { + SET_REQ_ERROR(req, GetLastError()); + } + + POST_COMPLETION_FOR_REQ(loop, req); + return 0; +} + + +static void uv_tty_queue_read_line(uv_loop_t* loop, uv_tty_t* handle) { + uv_req_t* req; + BOOL r; + + assert(handle->flags & UV_HANDLE_READING); + assert(!(handle->flags & UV_HANDLE_READ_PENDING)); + assert(handle->handle && handle->handle != INVALID_HANDLE_VALUE); + + req = &handle->read_req; + memset(&req->overlapped, 0, sizeof(req->overlapped)); + + handle->read_line_buffer = handle->alloc_cb((uv_handle_t*) handle, 8192); + assert(handle->read_line_buffer.base != NULL); + assert(handle->read_line_buffer.len > 0); + + /* Duplicate the console handle, so if we want to cancel the read, we can */ + /* just close this handle duplicate. */ + if (handle->read_line_handle == NULL) { + HANDLE this_process = GetCurrentProcess(); + r = DuplicateHandle(this_process, + handle->handle, + this_process, + &handle->read_line_handle, + 0, + 0, + DUPLICATE_SAME_ACCESS); + if (!r) { + handle->read_line_handle = NULL; + SET_REQ_ERROR(req, GetLastError()); + uv_insert_pending_req(loop, req); + goto out; + } + } + + r = QueueUserWorkItem(uv_tty_line_read_thread, + (void*) req, + WT_EXECUTELONGFUNCTION); + if (!r) { + SET_REQ_ERROR(req, GetLastError()); + uv_insert_pending_req(loop, req); + } + + out: + handle->flags |= UV_HANDLE_READ_PENDING; + handle->reqs_pending++; +} + + +static void uv_tty_queue_read(uv_loop_t* loop, uv_tty_t* handle) { + if (handle->flags & UV_HANDLE_TTY_RAW) { + uv_tty_queue_read_raw(loop, handle); + } else { + uv_tty_queue_read_line(loop, handle); + } +} + + +static const char* get_vt100_fn_key(DWORD code, char shift, char ctrl, + size_t* len) { +#define VK_CASE(vk, normal_str, shift_str, ctrl_str, shift_ctrl_str) \ + case (vk): \ + if (shift && ctrl) { \ + *len = sizeof shift_ctrl_str; \ + return "\033" shift_ctrl_str; \ + } else if (shift) { \ + *len = sizeof shift_str ; \ + return "\033" shift_str; \ + } else if (ctrl) { \ + *len = sizeof ctrl_str; \ + return "\033" ctrl_str; \ + } else { \ + *len = sizeof normal_str; \ + return "\033" normal_str; \ + } + + switch (code) { + /* These mappings are the same as Cygwin's. Unmodified and alt-modified */ + /* keypad keys comply with linux console, modifiers comply with xterm */ + /* modifier usage. F1..f12 and shift-f1..f10 comply with linux console, */ + /* f6..f12 with and without modifiers comply with rxvt. */ + VK_CASE(VK_INSERT, "[2~", "[2;2~", "[2;5~", "[2;6~") + VK_CASE(VK_END, "[4~", "[4;2~", "[4;5~", "[4;6~") + VK_CASE(VK_DOWN, "[B", "[1;2B", "[1;5B", "[1;6B") + VK_CASE(VK_NEXT, "[6~", "[6;2~", "[6;5~", "[6;6~") + VK_CASE(VK_LEFT, "[D", "[1;2D", "[1;5D", "[1;6D") + VK_CASE(VK_CLEAR, "[G", "[1;2G", "[1;5G", "[1;6G") + VK_CASE(VK_RIGHT, "[C", "[1;2C", "[1;5C", "[1;6C") + VK_CASE(VK_UP, "[A", "[1;2A", "[1;5A", "[1;6A") + VK_CASE(VK_HOME, "[1~", "[1;2~", "[1;5~", "[1;6~") + VK_CASE(VK_PRIOR, "[5~", "[5;2~", "[5;5~", "[5;6~") + VK_CASE(VK_DELETE, "[3~", "[3;2~", "[3;5~", "[3;6~") + VK_CASE(VK_NUMPAD0, "[2~", "[2;2~", "[2;5~", "[2;6~") + VK_CASE(VK_NUMPAD1, "[4~", "[4;2~", "[4;5~", "[4;6~") + VK_CASE(VK_NUMPAD2, "[B", "[1;2B", "[1;5B", "[1;6B") + VK_CASE(VK_NUMPAD3, "[6~", "[6;2~", "[6;5~", "[6;6~") + VK_CASE(VK_NUMPAD4, "[D", "[1;2D", "[1;5D", "[1;6D") + VK_CASE(VK_NUMPAD5, "[G", "[1;2G", "[1;5G", "[1;6G") + VK_CASE(VK_NUMPAD6, "[C", "[1;2C", "[1;5C", "[1;6C") + VK_CASE(VK_NUMPAD7, "[A", "[1;2A", "[1;5A", "[1;6A") + VK_CASE(VK_NUMPAD8, "[1~", "[1;2~", "[1;5~", "[1;6~") + VK_CASE(VK_NUMPAD9, "[5~", "[5;2~", "[5;5~", "[5;6~") + VK_CASE(VK_DECIMAL, "[3~", "[3;2~", "[3;5~", "[3;6~") + VK_CASE(VK_F1, "[[A", "[23~", "[11^", "[23^" ) + VK_CASE(VK_F2, "[[B", "[24~", "[12^", "[24^" ) + VK_CASE(VK_F3, "[[C", "[25~", "[13^", "[25^" ) + VK_CASE(VK_F4, "[[D", "[26~", "[14^", "[26^" ) + VK_CASE(VK_F5, "[[E", "[28~", "[15^", "[28^" ) + VK_CASE(VK_F6, "[17~", "[29~", "[17^", "[29^" ) + VK_CASE(VK_F7, "[18~", "[31~", "[18^", "[31^" ) + VK_CASE(VK_F8, "[19~", "[32~", "[19^", "[32^" ) + VK_CASE(VK_F9, "[20~", "[33~", "[20^", "[33^" ) + VK_CASE(VK_F10, "[21~", "[34~", "[21^", "[34^" ) + VK_CASE(VK_F11, "[23~", "[23$", "[23^", "[23@" ) + VK_CASE(VK_F12, "[24~", "[24$", "[24^", "[24@" ) + + default: + *len = 0; + return NULL; + } +#undef VK_CASE +} + + +void uv_process_tty_read_raw_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req) { + /* Shortcut for handle->last_input_record.Event.KeyEvent. */ +#define KEV handle->last_input_record.Event.KeyEvent + + DWORD records_left, records_read; + uv_buf_t buf; + off_t buf_used; + + assert(handle->type == UV_TTY); + handle->flags &= ~UV_HANDLE_READ_PENDING; + + if (!(handle->flags & UV_HANDLE_READING) || + !(handle->flags & UV_HANDLE_TTY_RAW)) { + goto out; + } + + if (!REQ_SUCCESS(req)) { + /* An error occurred while waiting for the event. */ + if ((handle->flags & UV_HANDLE_READING)) { + handle->flags &= ~UV_HANDLE_READING; + loop->last_error = GET_REQ_UV_ERROR(req); + handle->read_cb((uv_stream_t*)handle, -1, uv_null_buf_); + } + goto out; + } + + /* Fetch the number of events */ + if (!GetNumberOfConsoleInputEvents(handle->handle, &records_left)) { + handle->flags &= ~UV_HANDLE_READING; + uv_set_sys_error(loop, GetLastError()); + handle->read_cb((uv_stream_t*)handle, -1, uv_null_buf_); + goto out; + } + + /* Windows sends a lot of events that we're not interested in, so buf */ + /* will be allocated on demand, when there's actually something to emit. */ + buf = uv_null_buf_; + buf_used = 0; + + while ((records_left > 0 || handle->last_key_len > 0) && + (handle->flags & UV_HANDLE_READING)) { + if (handle->last_key_len == 0) { + /* Read the next input record */ + if (!ReadConsoleInputW(handle->handle, + &handle->last_input_record, + 1, + &records_read)) { + uv_set_sys_error(loop, GetLastError()); + handle->flags &= ~UV_HANDLE_READING; + handle->read_cb((uv_stream_t*) handle, -1, buf); + goto out; + } + records_left--; + + /* Ignore events that are not keyboard events */ + if (handle->last_input_record.EventType != KEY_EVENT) { + continue; + } + + /* Ignore keyup events, unless the left alt key was held and a valid */ + /* unicode character was emitted. */ + if (!KEV.bKeyDown && !(((KEV.dwControlKeyState & LEFT_ALT_PRESSED) || + KEV.wVirtualKeyCode==VK_MENU) && KEV.uChar.UnicodeChar != 0)) { + continue; + } + + /* Ignore keypresses to numpad number keys if the left alt is held */ + /* because the user is composing a character, or windows simulating */ + /* this. */ + if ((KEV.dwControlKeyState & LEFT_ALT_PRESSED) && + !(KEV.dwControlKeyState & ENHANCED_KEY) && + (KEV.wVirtualKeyCode == VK_INSERT || + KEV.wVirtualKeyCode == VK_END || + KEV.wVirtualKeyCode == VK_DOWN || + KEV.wVirtualKeyCode == VK_NEXT || + KEV.wVirtualKeyCode == VK_LEFT || + KEV.wVirtualKeyCode == VK_CLEAR || + KEV.wVirtualKeyCode == VK_RIGHT || + KEV.wVirtualKeyCode == VK_HOME || + KEV.wVirtualKeyCode == VK_UP || + KEV.wVirtualKeyCode == VK_PRIOR || + KEV.wVirtualKeyCode == VK_NUMPAD0 || + KEV.wVirtualKeyCode == VK_NUMPAD1 || + KEV.wVirtualKeyCode == VK_NUMPAD2 || + KEV.wVirtualKeyCode == VK_NUMPAD3 || + KEV.wVirtualKeyCode == VK_NUMPAD4 || + KEV.wVirtualKeyCode == VK_NUMPAD5 || + KEV.wVirtualKeyCode == VK_NUMPAD6 || + KEV.wVirtualKeyCode == VK_NUMPAD7 || + KEV.wVirtualKeyCode == VK_NUMPAD8 || + KEV.wVirtualKeyCode == VK_NUMPAD9)) { + continue; + } + + if (KEV.uChar.UnicodeChar != 0) { + int prefix_len, char_len; + + /* Character key pressed */ + if (KEV.uChar.UnicodeChar >= 0xD800 && + KEV.uChar.UnicodeChar < 0xDC00) { + /* UTF-16 high surrogate */ + handle->last_utf16_high_surrogate = KEV.uChar.UnicodeChar; + continue; + } + + /* Prefix with \u033 if alt was held, but alt was not used as part */ + /* a compose sequence. */ + if ((KEV.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) + && !(KEV.dwControlKeyState & (LEFT_CTRL_PRESSED | + RIGHT_CTRL_PRESSED)) && KEV.bKeyDown) { + handle->last_key[0] = '\033'; + prefix_len = 1; + } else { + prefix_len = 0; + } + + if (KEV.uChar.UnicodeChar >= 0xDC00 && + KEV.uChar.UnicodeChar < 0xE000) { + /* UTF-16 surrogate pair */ + WCHAR utf16_buffer[2] = { handle->last_utf16_high_surrogate, + KEV.uChar.UnicodeChar}; + char_len = WideCharToMultiByte(CP_UTF8, + 0, + utf16_buffer, + 2, + &handle->last_key[prefix_len], + sizeof handle->last_key, + NULL, + NULL); + } else { + /* Single UTF-16 character */ + char_len = WideCharToMultiByte(CP_UTF8, + 0, + &KEV.uChar.UnicodeChar, + 1, + &handle->last_key[prefix_len], + sizeof handle->last_key, + NULL, + NULL); + } + + /* Whatever happened, the last character wasn't a high surrogate. */ + handle->last_utf16_high_surrogate = 0; + + /* If the utf16 character(s) couldn't be converted something must */ + /* be wrong. */ + if (!char_len) { + uv_set_sys_error(loop, GetLastError()); + handle->flags &= ~UV_HANDLE_READING; + handle->read_cb((uv_stream_t*) handle, -1, buf); + goto out; + } + + handle->last_key_len = (unsigned char) (prefix_len + char_len); + handle->last_key_offset = 0; + continue; + + } else { + /* Function key pressed */ + const char* vt100; + size_t prefix_len, vt100_len; + + vt100 = get_vt100_fn_key(KEV.wVirtualKeyCode, + !!(KEV.dwControlKeyState & SHIFT_PRESSED), + !!(KEV.dwControlKeyState & ( + LEFT_CTRL_PRESSED | + RIGHT_CTRL_PRESSED)), + &vt100_len); + + /* If we were unable to map to a vt100 sequence, just ignore. */ + if (!vt100) { + continue; + } + + /* Prefix with \x033 when the alt key was held. */ + if (KEV.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) { + handle->last_key[0] = '\033'; + prefix_len = 1; + } else { + prefix_len = 0; + } + + /* Copy the vt100 sequence to the handle buffer. */ + assert(prefix_len + vt100_len < sizeof handle->last_key); + memcpy(&handle->last_key[prefix_len], vt100, vt100_len); + + handle->last_key_len = (unsigned char) (prefix_len + vt100_len); + handle->last_key_offset = 0; + continue; + } + } else { + /* Copy any bytes left from the last keypress to the user buffer. */ + if (handle->last_key_offset < handle->last_key_len) { + /* Allocate a buffer if needed */ + if (buf_used == 0) { + buf = handle->alloc_cb((uv_handle_t*) handle, 1024); + } + + buf.base[buf_used++] = handle->last_key[handle->last_key_offset++]; + + /* If the buffer is full, emit it */ + if (buf_used == buf.len) { + handle->read_cb((uv_stream_t*) handle, buf_used, buf); + buf = uv_null_buf_; + buf_used = 0; + } + + continue; + } + + /* Apply dwRepeat from the last input record. */ + if (--KEV.wRepeatCount > 0) { + handle->last_key_offset = 0; + continue; + } + + handle->last_key_len = 0; + continue; + } + } + + /* Send the buffer back to the user */ + if (buf_used > 0) { + handle->read_cb((uv_stream_t*) handle, buf_used, buf); + } + + out: + /* Wait for more input events. */ + if ((handle->flags & UV_HANDLE_READING) && + !(handle->flags & UV_HANDLE_READ_PENDING)) { + uv_tty_queue_read(loop, handle); + } + + DECREASE_PENDING_REQ_COUNT(handle); + +#undef KEV +} + + + +void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req) { + uv_buf_t buf; + + assert(handle->type == UV_TTY); + + buf = handle->read_line_buffer; + + handle->flags &= ~UV_HANDLE_READ_PENDING; + handle->read_line_buffer = uv_null_buf_; + + if (!REQ_SUCCESS(req)) { + /* Read was not successful */ + if ((handle->flags & UV_HANDLE_READING) && + !(handle->flags & UV_HANDLE_TTY_RAW)) { + /* Real error */ + handle->flags &= ~UV_HANDLE_READING; + loop->last_error = GET_REQ_UV_ERROR(req); + handle->read_cb((uv_stream_t*) handle, -1, buf); + } else { + /* The read was cancelled, or whatever we don't care */ + uv_set_sys_error(loop, WSAEWOULDBLOCK); /* maps to UV_EAGAIN */ + handle->read_cb((uv_stream_t*) handle, 0, buf); + } + + } else { + /* Read successful */ + /* TODO: read unicode, convert to utf-8 */ + DWORD bytes = req->overlapped.InternalHigh; + if (bytes == 0) { + uv_set_sys_error(loop, WSAEWOULDBLOCK); /* maps to UV_EAGAIN */ + } + handle->read_cb((uv_stream_t*) handle, bytes, buf); + } + + /* Wait for more input events. */ + if ((handle->flags & UV_HANDLE_READING) && + !(handle->flags & UV_HANDLE_READ_PENDING)) { + uv_tty_queue_read(loop, handle); + } + + DECREASE_PENDING_REQ_COUNT(handle); +} + + +void uv_process_tty_read_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* req) { + + /* If the read_line_buffer member is zero, it must have been an raw read. */ + /* Otherwise it was a line-buffered read. */ + /* FIXME: This is quite obscure. Use a flag or something. */ + if (handle->read_line_buffer.len == 0) { + uv_process_tty_read_raw_req(loop, handle, req); + } else { + uv_process_tty_read_line_req(loop, handle, req); + } +} + + +int uv_tty_read_start(uv_tty_t* handle, uv_alloc_cb alloc_cb, + uv_read_cb read_cb) { + uv_loop_t* loop = handle->loop; + + handle->flags |= UV_HANDLE_READING; + handle->read_cb = read_cb; + handle->alloc_cb = alloc_cb; + + /* If reading was stopped and then started again, there could stell be a */ + /* read request pending. */ + if (handle->flags & UV_HANDLE_READ_PENDING) { + return 0; + } + + /* Maybe the user stopped reading half-way while processing key events. */ + /* Short-circuit if this could be the case. */ + if (handle->last_key_len > 0) { + SET_REQ_SUCCESS(&handle->read_req); + uv_insert_pending_req(handle->loop, (uv_req_t*) &handle->read_req); + return -1; + } + + uv_tty_queue_read(loop, handle); + + return 0; +} + + +int uv_tty_read_stop(uv_tty_t* handle) { + handle->flags &= ~UV_HANDLE_READING; + + /* Cancel raw read */ + if ((handle->flags & UV_HANDLE_READ_PENDING) && + (handle->flags & UV_HANDLE_TTY_RAW)) { + /* Write some bullshit event to force the console wait to return. */ + INPUT_RECORD record; + DWORD written; + memset(&record, 0, sizeof record); + if (!WriteConsoleInputW(handle->handle, &record, 1, &written)) { + uv_set_sys_error(handle->loop, GetLastError()); + return -1; + } + } + + /* Cancel line-buffered read */ + if (handle->read_line_handle != NULL) { + /* Closing this handle will cancel the ReadConsole operation */ + CloseHandle(handle->read_line_handle); + handle->read_line_handle = NULL; + } + + + return 0; +} + + +static void uv_tty_update_virtual_window(CONSOLE_SCREEN_BUFFER_INFO* info) { + uv_tty_virtual_height = info->srWindow.Bottom - info->srWindow.Top + 1; + uv_tty_virtual_width = info->dwSize.X; - if (r) { - return UV_TTY; + /* Recompute virtual window offset row. */ + if (uv_tty_virtual_offset == -1) { + uv_tty_virtual_offset = info->dwCursorPosition.Y; + } else if (uv_tty_virtual_offset < info->dwCursorPosition.Y - + uv_tty_virtual_height + 1) { + /* If suddenly find the cursor outside of the virtual window, it must */ + /* have somehow scrolled. Update the virtual window offset. */ + uv_tty_virtual_offset = info->dwCursorPosition.Y - + uv_tty_virtual_height + 1; } + if (uv_tty_virtual_offset + uv_tty_virtual_height > info->dwSize.Y) { + uv_tty_virtual_offset = info->dwSize.Y - uv_tty_virtual_height; + } + if (uv_tty_virtual_offset < 0) { + uv_tty_virtual_offset = 0; + } +} + + +static COORD uv_tty_make_real_coord(uv_tty_t* handle, + CONSOLE_SCREEN_BUFFER_INFO* info, int x, unsigned char x_relative, int y, + unsigned char y_relative) { + COORD result; + + uv_tty_update_virtual_window(info); + + /* Adjust y position */ + if (y_relative) { + y = info->dwCursorPosition.Y + y; + } else { + y = uv_tty_virtual_offset + y; + } + /* Clip y to virtual client rectangle */ + if (y < uv_tty_virtual_offset) { + y = uv_tty_virtual_offset; + } else if (y >= uv_tty_virtual_offset + uv_tty_virtual_height) { + y = uv_tty_virtual_offset + uv_tty_virtual_height - 1; + } + + /* Adjust x */ + if (x_relative) { + x = info->dwCursorPosition.X + x; + } + /* Clip x */ + if (x < 0) { + x = 0; + } else if (x >= uv_tty_virtual_width) { + x = uv_tty_virtual_width - 1; + } + + result.X = (unsigned short) x; + result.Y = (unsigned short) y; + return result; +} + + +static int uv_tty_emit_text(uv_tty_t* handle, WCHAR buffer[], DWORD length, + DWORD* error) { + DWORD written; + + if (*error != ERROR_SUCCESS) { + return -1; + } + + if (!WriteConsoleW(handle->handle, + (void*) buffer, + length, + &written, + NULL)) { + *error = GetLastError(); + return -1; + } + + return 0; +} + + +static int uv_tty_move_caret(uv_tty_t* handle, int x, unsigned char x_relative, + int y, unsigned char y_relative, DWORD* error) { + CONSOLE_SCREEN_BUFFER_INFO info; + COORD pos; + + if (*error != ERROR_SUCCESS) { + return -1; + } + + retry: + if (!GetConsoleScreenBufferInfo(handle->handle, &info)) { + *error = GetLastError(); + } + + pos = uv_tty_make_real_coord(handle, &info, x, x_relative, y, y_relative); + + if (!SetConsoleCursorPosition(handle->handle, pos)) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + /* The console may be resized - retry */ + goto retry; + } else { + *error = GetLastError(); + return -1; + } + } + + return 0; +} + + +static int uv_tty_clear(uv_tty_t* handle, int dir, char entire_screen, + DWORD* error) { + unsigned short argc = handle->ansi_csi_argc; + unsigned short* argv = handle->ansi_csi_argv; + + CONSOLE_SCREEN_BUFFER_INFO info; + COORD start, end; + DWORD count, written; + + int x1, x2, y1, y2; + int x1r, x2r, y1r, y2r; + + if (*error != ERROR_SUCCESS) { + return -1; + } + + if (dir == 0) { + /* Clear from current position */ + x1 = 0; + x1r = 1; + } else { + /* Clear from column 0 */ + x1 = 0; + x1r = 0; + } + + if (dir == 1) { + /* Clear to current position */ + x2 = 0; + x2r = 1; + } else { + /* Clear to end of row. We pretend the console is 65536 characters wide, */ + /* uv_tty_make_real_coord will clip it to the actual console width. */ + x2 = 0xffff; + x2r = 0; + } + + if (!entire_screen) { + /* Stay on our own row */ + y1 = y2 = 0; + y1r = y2r = 1; + } else { + /* Apply columns direction to row */ + y1 = x1; + y1r = x1r; + y2 = x2; + y2r = x2r; + } + + retry: + if (!GetConsoleScreenBufferInfo(handle->handle, &info)) { + *error = GetLastError(); + return -1; + } + + start = uv_tty_make_real_coord(handle, &info, x1, x1r, y1, y1r); + end = uv_tty_make_real_coord(handle, &info, x2, x2r, y2, y2r); + count = (end.Y * info.dwSize.X + end.X) - + (start.Y * info.dwSize.X + start.X) + 1; + + if (!(FillConsoleOutputCharacterW(handle->handle, + L'\x20', + count, + start, + &written) && + FillConsoleOutputAttribute(handle->handle, + info.wAttributes, + written, + start, + &written))) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + /* The console may be resized - retry */ + goto retry; + } else { + *error = GetLastError(); + return -1; + } + } + + return 0; +} + + +static int uv_tty_set_style(uv_tty_t* handle, DWORD* error) { + unsigned short argc = handle->ansi_csi_argc; + unsigned short* argv = handle->ansi_csi_argv; + int i; + CONSOLE_SCREEN_BUFFER_INFO info; + + char fg_color = -1, bg_color = -1; + char fg_bright = -1, bg_bright = -1; + + if (argc == 0) { + /* Reset mode */ + fg_color = 7; + bg_color = 0; + fg_bright = 0; + bg_bright = 0; + } + + for (i = 0; i < argc; i++) { + short arg = argv[i]; + + if (arg == 0) { + /* Reset mode */ + fg_color = 7; + bg_color = 0; + fg_bright = 0; + bg_bright = 0; + + } else if (arg == 1) { + /* Bright */ + fg_bright = 1; + + } else if (arg == 21 || arg == 22) { + /* Bright off. */ + fg_bright = 0; + + } else if (arg >= 30 && arg <= 37) { + /* Set foreground color */ + fg_color = arg - 30; + + } else if (arg == 39) { + /* Default text color */ + fg_color = 7; + + } else if (arg >= 40 && arg <= 47) { + /* Set background color */ + bg_color = arg - 40; + + } else if (arg == 49) { + /* Default background color */ + bg_color = 0; + } + } + + if (fg_color == -1 && bg_color == -1 && fg_bright == -1 && + bg_bright == -1) { + /* Nothing changed */ + return 0; + } + + if (!GetConsoleScreenBufferInfo(handle->handle, &info)) { + *error = GetLastError(); + return -1; + } + + if (fg_color != -1) { + info.wAttributes &= ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + if (fg_color & 1) info.wAttributes |= FOREGROUND_RED; + if (fg_color & 2) info.wAttributes |= FOREGROUND_GREEN; + if (fg_color & 4) info.wAttributes |= FOREGROUND_BLUE; + } + + if (fg_bright != -1) { + if (fg_bright) { + info.wAttributes |= FOREGROUND_INTENSITY; + } else { + info.wAttributes &= ~FOREGROUND_INTENSITY; + } + } + + if (bg_color != -1) { + info.wAttributes &= ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); + if (bg_color & 1) info.wAttributes |= BACKGROUND_RED; + if (bg_color & 2) info.wAttributes |= BACKGROUND_GREEN; + if (bg_color & 4) info.wAttributes |= BACKGROUND_BLUE; + } + + if (bg_bright != -1) { + if (bg_bright) { + info.wAttributes |= BACKGROUND_INTENSITY; + } else { + info.wAttributes &= ~BACKGROUND_INTENSITY; + } + } + + if (!SetConsoleTextAttribute(handle->handle, info.wAttributes)) { + *error = GetLastError(); + return -1; + } + + return 0; +} + + +static int uv_tty_write_bufs(uv_tty_t* handle, uv_buf_t bufs[], int bufcnt, + DWORD* error) { + /* We can only write 8k characters at a time. Windows can't handle */ + /* much more characters in a single console write anyway. */ + WCHAR utf16_buf[8192]; + DWORD utf16_buf_used = 0; + int i; + +#define FLUSH_TEXT() \ + do { \ + if (utf16_buf_used > 0) { \ + uv_tty_emit_text(handle, utf16_buf, utf16_buf_used, error); \ + utf16_buf_used = 0; \ + } \ + } while (0) + + /* Cache for fast access */ + unsigned char utf8_bytes_left = handle->utf8_bytes_left; + unsigned int utf8_codepoint = handle->utf8_codepoint; + unsigned char previous_eol = handle->previous_eol; + unsigned char ansi_parser_state = handle->ansi_parser_state; + + /* Store the error here. If we encounter an error, stop trying to do i/o */ + /* but keep parsing the buffer so we leave the parser in a consistent */ + /* state. */ + *error = ERROR_SUCCESS; + + EnterCriticalSection(&uv_tty_output_lock); + + for (i = 0; i < bufcnt; i++) { + uv_buf_t buf = bufs[i]; + unsigned int j; + + for (j = 0; j < buf.len; j++) { + unsigned char c = buf.base[j]; + + /* Run the character through the utf8 decoder We happily accept non */ + /* shortest form encodings and invalid code points - there's no real */ + /* harm that can be done. */ + if (utf8_bytes_left == 0) { + /* Read utf-8 start byte */ + DWORD first_zero_bit; + unsigned char not_c = ~c; +#ifdef _MSC_VER /* msvc */ + if (_BitScanReverse(&first_zero_bit, not_c)) { +#else /* assume gcc */ + if (first_zero_bit = __builtin_clzl(not_c), c != 0) { +#endif + if (first_zero_bit == 7) { + /* Ascii - pass right through */ + utf8_codepoint = (unsigned int) c; + + } else if (first_zero_bit <= 5) { + /* Multibyte sequence */ + utf8_codepoint = (0xff >> (8 - first_zero_bit)) & c; + utf8_bytes_left = (char) (6 - first_zero_bit); + + } else { + /* Invalid continuation */ + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + } + + } else { + /* 0xff -- invalid */ + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + } + + } else if ((c & 0xc0) == 0x80) { + /* Valid continuation of utf-8 multibyte sequence */ + utf8_bytes_left--; + utf8_codepoint <<= 6; + utf8_codepoint |= ((unsigned int) c & 0x3f); + + } else { + /* Start byte where continuation was expected. */ + utf8_bytes_left = 0; + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + /* Patch buf offset so this character will be parsed again as a */ + /* start byte. */ + j--; + } + + /* Maybe we need to parse more bytes to find a character. */ + if (utf8_bytes_left != 0) { + continue; + } + + /* Parse vt100/ansi escape codes */ + if (ansi_parser_state == ANSI_NORMAL) { + switch (utf8_codepoint) { + case '\033': + ansi_parser_state = ANSI_ESCAPE_SEEN; + continue; + + case 0233: + ansi_parser_state = ANSI_CSI; + handle->ansi_csi_argc = 0; + continue; + } + + } else if (ansi_parser_state == ANSI_ESCAPE_SEEN) { + switch (utf8_codepoint) { + case '[': + ansi_parser_state = ANSI_CSI; + handle->ansi_csi_argc = 0; + continue; + + case '^': + case '_': + case 'P': + case ']': + /* Not supported, but we'll have to parse until we see a stop */ + /* code, e.g. ESC \ or BEL. */ + ansi_parser_state = ANSI_ST_CONTROL; + continue; + + case '\033': + /* Ignore double escape. */ + continue; + + default: + if (utf8_codepoint >= '@' && utf8_codepoint <= '_') { + /* Single-char control. */ + ansi_parser_state = ANSI_NORMAL; + continue; + } else { + /* Invalid - proceed as normal, */ + ansi_parser_state = ANSI_NORMAL; + } + } + + } else if (ansi_parser_state & ANSI_CSI) { + if (!(ansi_parser_state & ANSI_IGNORE)) { + if (utf8_codepoint >= '0' && utf8_codepoint <= '9') { + /* Parsing a numerical argument */ + + if (!(ansi_parser_state & ANSI_IN_ARG)) { + /* We were not currently parsing a number */ + + /* Check for too many arguments */ + if (handle->ansi_csi_argc >= COUNTOF(handle->ansi_csi_argv)) { + ansi_parser_state |= ANSI_IGNORE; + continue; + } + + ansi_parser_state |= ANSI_IN_ARG; + handle->ansi_csi_argc++; + handle->ansi_csi_argv[handle->ansi_csi_argc - 1] = + (unsigned short) utf8_codepoint - '0'; + continue; + } else { + /* We were already parsing a number. Parse next digit. */ + uint32_t value = 10 * + handle->ansi_csi_argv[handle->ansi_csi_argc - 1]; + + /* Check for overflow. */ + if (value > UINT16_MAX) { + ansi_parser_state |= ANSI_IGNORE; + continue; + } + + handle->ansi_csi_argv[handle->ansi_csi_argc - 1] = + (unsigned short) value + (utf8_codepoint - '0'); + continue; + } + + } else if (utf8_codepoint == ';') { + /* Denotes the end of an argument. */ + if (ansi_parser_state & ANSI_IN_ARG) { + ansi_parser_state &= ~ANSI_IN_ARG; + continue; + + } else { + /* If ANSI_IN_ARG is not set, add another argument and */ + /* default it to 0. */ + /* Check for too many arguments */ + if (handle->ansi_csi_argc >= COUNTOF(handle->ansi_csi_argv)) { + ansi_parser_state |= ANSI_IGNORE; + continue; + } + + handle->ansi_csi_argc++; + handle->ansi_csi_argv[handle->ansi_csi_argc - 1] = 0; + continue; + } + + } else if (utf8_codepoint >= '@' && utf8_codepoint <= '~' && + (handle->ansi_csi_argc > 0 || utf8_codepoint != '[')) { + int x, y, d; + + /* Command byte */ + switch (utf8_codepoint) { + case 'A': + /* cursor up */ + FLUSH_TEXT(); + y = -(handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1); + uv_tty_move_caret(handle, 0, 1, y, 1, error); + break; + + case 'B': + /* cursor down */ + FLUSH_TEXT(); + y = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1; + uv_tty_move_caret(handle, 0, 1, y, 1, error); + break; + + case 'C': + /* cursor forward */ + FLUSH_TEXT(); + x = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1; + uv_tty_move_caret(handle, x, 1, 0, 1, error); + break; + + case 'D': + /* cursor back */ + FLUSH_TEXT(); + x = -(handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1); + uv_tty_move_caret(handle, x, 1, 0, 1, error); + break; + + case 'E': + /* cursor next line */ + FLUSH_TEXT(); + y = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1; + uv_tty_move_caret(handle, 0, 0, y, 1, error); + break; + + case 'F': + /* cursor previous line */ + FLUSH_TEXT(); + y = -(handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 1); + uv_tty_move_caret(handle, 0, 0, y, 1, error); + break; + + case 'G': + /* cursor horizontal move absolute */ + FLUSH_TEXT(); + x = (handle->ansi_csi_argc >= 1 && handle->ansi_csi_argv[0]) + ? handle->ansi_csi_argv[0] - 1 : 0; + uv_tty_move_caret(handle, x, 0, 0, 1, error); + break; + + case 'H': + case 'f': + /* cursor move absolute */ + FLUSH_TEXT(); + y = (handle->ansi_csi_argc >= 1 && handle->ansi_csi_argv[0]) + ? handle->ansi_csi_argv[0] - 1 : 0; + x = (handle->ansi_csi_argc >= 2 && handle->ansi_csi_argv[1]) + ? handle->ansi_csi_argv[1] - 1 : 0; + uv_tty_move_caret(handle, x, 0, y, 0, error); + break; + + case 'J': + /* Erase screen */ + FLUSH_TEXT(); + d = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 0; + if (d >= 0 && d <= 2) { + uv_tty_clear(handle, d, 1, error); + } + break; + + case 'K': + /* Erase line */ + FLUSH_TEXT(); + d = handle->ansi_csi_argc ? handle->ansi_csi_argv[0] : 0; + if (d >= 0 && d <= 2) { + uv_tty_clear(handle, d, 0, error); + } + break; + + case 'm': + /* Set style */ + FLUSH_TEXT(); + uv_tty_set_style(handle, error); + break; + } + + /* Sequence ended - go back to normal state. */ + ansi_parser_state = ANSI_NORMAL; + continue; + + } else { + /* We don't support commands that use private mode characters or */ + /* intermediaries. Ignore the rest of the sequence. */ + ansi_parser_state |= ANSI_IGNORE; + continue; + } + } else { + /* We're ignoring this command. Stop only on command character. */ + if (utf8_codepoint >= '@' && utf8_codepoint <= '~') { + ansi_parser_state = ANSI_NORMAL; + } + continue; + } + + } else if (ansi_parser_state & ANSI_ST_CONTROL) { + /* Unsupported control code */ + /* Ignore everything until we see BEL or ESC \ */ + if (ansi_parser_state & ANSI_IN_STRING) { + if (!(ansi_parser_state & ANSI_BACKSLASH_SEEN)) { + if (utf8_codepoint == '"') { + ansi_parser_state &= ~ANSI_IN_STRING; + } else if (utf8_codepoint == '\\') { + ansi_parser_state |= ANSI_BACKSLASH_SEEN; + } + } else { + ansi_parser_state &= ~ANSI_BACKSLASH_SEEN; + } + } else { + if (utf8_codepoint == '\007' || (utf8_codepoint == '\\' && + (ansi_parser_state & ANSI_ESCAPE_SEEN))) { + /* End of sequence */ + ansi_parser_state = ANSI_NORMAL; + } else if (utf8_codepoint == '\033') { + /* Escape character */ + ansi_parser_state |= ANSI_ESCAPE_SEEN; + } else if (utf8_codepoint == '"') { + /* String starting */ + ansi_parser_state |= ANSI_IN_STRING; + ansi_parser_state &= ~ANSI_ESCAPE_SEEN; + ansi_parser_state &= ~ANSI_BACKSLASH_SEEN; + } else { + ansi_parser_state &= ~ANSI_ESCAPE_SEEN; + } + } + continue; + } else { + /* Inconsistent state */ + abort(); + } + + /* We wouldn't mind emitting utf-16 surrogate pairs. Too bad, the */ + /* windows console doesn't really support UTF-16, so just emit the */ + /* replacement character. */ + if (utf8_codepoint > 0xffff) { + utf8_codepoint = UNICODE_REPLACEMENT_CHARACTER; + } + + if (utf8_codepoint == 0x0a || utf8_codepoint == 0x0d) { + /* EOL conversion - emit \r\n, when we see either \r or \n. */ + /* If a \n immediately follows a \r or vice versa, ignore it. */ + if (previous_eol == 0 || utf8_codepoint == previous_eol) { + /* If there's no room in the utf16 buf, flush it first. */ + if (2 > COUNTOF(utf16_buf) - utf16_buf_used) { + uv_tty_emit_text(handle, utf16_buf, utf16_buf_used, error); + utf16_buf_used = 0; + } + + utf16_buf[utf16_buf_used++] = L'\r'; + utf16_buf[utf16_buf_used++] = L'\n'; + previous_eol = (char) utf8_codepoint; + } else { + /* Ignore this newline, but don't ignore later ones. */ + previous_eol = 0; + } + + } else if (utf8_codepoint <= 0xffff) { + /* Encode character into utf-16 buffer. */ + + /* If there's no room in the utf16 buf, flush it first. */ + if (1 > COUNTOF(utf16_buf) - utf16_buf_used) { + uv_tty_emit_text(handle, utf16_buf, utf16_buf_used, error); + utf16_buf_used = 0; + } + + utf16_buf[utf16_buf_used++] = (WCHAR) utf8_codepoint; + previous_eol = 0; + } + } + } + + /* Flush remaining characters */ + FLUSH_TEXT(); + + /* Copy cached values back to struct. */ + handle->utf8_bytes_left = utf8_bytes_left; + handle->utf8_codepoint = utf8_codepoint; + handle->previous_eol = previous_eol; + handle->ansi_parser_state = ansi_parser_state; + + LeaveCriticalSection(&uv_tty_output_lock); + + if (*error == STATUS_SUCCESS) { + return 0; + } else { + return -1; + } + +#undef FLUSH_TEXT +} + + +int uv_tty_write(uv_loop_t* loop, uv_write_t* req, uv_tty_t* handle, + uv_buf_t bufs[], int bufcnt, uv_write_cb cb) { + DWORD error; + + if ((handle->flags & UV_HANDLE_SHUTTING) || + (handle->flags & UV_HANDLE_CLOSING)) { + uv_set_sys_error(loop, WSAESHUTDOWN); + return -1; + } + + uv_req_init(loop, (uv_req_t*) req); + req->type = UV_WRITE; + req->handle = (uv_stream_t*) handle; + req->cb = cb; + + handle->reqs_pending++; + handle->write_reqs_pending++; + + req->queued_bytes = 0; + + if (!uv_tty_write_bufs(handle, bufs, bufcnt, &error)) { + SET_REQ_SUCCESS(req); + } else { + SET_REQ_ERROR(req, error); + } + + uv_insert_pending_req(loop, (uv_req_t*) req); + + return 0; +} + + +void uv_process_tty_write_req(uv_loop_t* loop, uv_tty_t* handle, + uv_write_t* req) { + + handle->write_queue_size -= req->queued_bytes; + + if (req->cb) { + loop->last_error = GET_REQ_UV_ERROR(req); + ((uv_write_cb)req->cb)(req, loop->last_error.code == UV_OK ? 0 : -1); + } + + handle->write_reqs_pending--; + if (handle->flags & UV_HANDLE_SHUTTING && + handle->write_reqs_pending == 0) { + uv_want_endgame(loop, (uv_handle_t*)handle); + } + + DECREASE_PENDING_REQ_COUNT(handle); +} + + +void uv_tty_close(uv_tty_t* handle) { + uv_tty_read_stop(handle); + CloseHandle(handle->handle); + + if (handle->reqs_pending == 0) { + uv_want_endgame(handle->loop, (uv_handle_t*) handle); + } +} + + +void uv_tty_endgame(uv_loop_t* loop, uv_tty_t* handle) { + if (handle->flags & UV_HANDLE_CONNECTION && + handle->flags & UV_HANDLE_SHUTTING && + !(handle->flags & UV_HANDLE_SHUT) && + handle->write_reqs_pending == 0) { + handle->flags |= UV_HANDLE_SHUT; + + /* TTY shutdown is really just a no-op */ + if (handle->shutdown_req->cb) { + handle->shutdown_req->cb(handle->shutdown_req, 0); + } + + DECREASE_PENDING_REQ_COUNT(handle); + return; + } + + if (handle->flags & UV_HANDLE_CLOSING && + handle->reqs_pending == 0) { + /* The console handle duplicate used for line reading should be destroyed */ + /* by uv_tty_read_stop. */ + assert(handle->read_line_handle == NULL); + + /* The wait handle used for raw reading should be unregistered when the */ + /* wait callback runs. */ + assert(handle->read_raw_wait == NULL); + + assert(!(handle->flags & UV_HANDLE_CLOSED)); + handle->flags |= UV_HANDLE_CLOSED; + + if (handle->close_cb) { + handle->close_cb((uv_handle_t*)handle); + } + + uv_unref(loop); + } +} + + +/* TODO: remove me */ +void uv_process_tty_accept_req(uv_loop_t* loop, uv_tty_t* handle, + uv_req_t* raw_req) { + abort(); +} - assert(0 && "implement me"); - return UV_UNKNOWN_HANDLE; +/* TODO: remove me */ +void uv_process_tty_connect_req(uv_loop_t* loop, uv_tty_t* handle, + uv_connect_t* req) { + abort(); } diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h index bf376e3914..1d243c48f2 100644 --- a/deps/uv/test/test-list.h +++ b/deps/uv/test/test-list.h @@ -22,6 +22,8 @@ TEST_DECLARE (tty) TEST_DECLARE (tcp_ping_pong) TEST_DECLARE (tcp_ping_pong_v6) +TEST_DECLARE (tcp_ref) +TEST_DECLARE (tcp_ref2) TEST_DECLARE (pipe_ping_pong) TEST_DECLARE (delayed_accept) TEST_DECLARE (tcp_writealot) @@ -52,6 +54,8 @@ TEST_DECLARE (connection_fail_doesnt_auto_close) TEST_DECLARE (shutdown_eof) TEST_DECLARE (callback_stack) TEST_DECLARE (timer) +TEST_DECLARE (timer_ref) +TEST_DECLARE (timer_ref2) TEST_DECLARE (timer_again) TEST_DECLARE (idle_starvation) TEST_DECLARE (loop_handles) @@ -104,6 +108,12 @@ HELPER_DECLARE (pipe_echo_server) TASK_LIST_START TEST_ENTRY (tty) + + TEST_ENTRY (tcp_ref) + + TEST_ENTRY (tcp_ref2) + TEST_HELPER (tcp_ref2, tcp4_echo_server) + TEST_ENTRY (tcp_ping_pong) TEST_HELPER (tcp_ping_pong, tcp4_echo_server) @@ -154,6 +164,8 @@ TASK_LIST_START TEST_HELPER (callback_stack, tcp4_echo_server) TEST_ENTRY (timer) + TEST_ENTRY (timer_ref) + TEST_ENTRY (timer_ref2) TEST_ENTRY (timer_again) diff --git a/deps/uv/test/test-tcp-close.c b/deps/uv/test/test-tcp-close.c index 5da8a84f8a..f5dd0a4405 100644 --- a/deps/uv/test/test-tcp-close.c +++ b/deps/uv/test/test-tcp-close.c @@ -127,3 +127,50 @@ TEST_IMPL(tcp_close) { return 0; } + + +TEST_IMPL(tcp_ref) { + uv_tcp_t never; + int r; + + /* A tcp just initialized should count as one reference. */ + r = uv_tcp_init(uv_default_loop(), &never); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +} + + +static void never_cb(uv_connect_t* conn_req, int status) { + FATAL("never_cb should never be called"); +} + + +TEST_IMPL(tcp_ref2) { + uv_tcp_t never; + int r; + + /* A tcp just initialized should count as one reference. */ + r = uv_tcp_init(uv_default_loop(), &never); + ASSERT(r == 0); + + r = uv_tcp_connect(&connect_req, + &never, + uv_ip4_addr("127.0.0.1", TEST_PORT), + never_cb); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +} diff --git a/deps/uv/test/test-tcp-write-error.c b/deps/uv/test/test-tcp-write-error.c index f3d12b8d6b..c5907676be 100644 --- a/deps/uv/test/test-tcp-write-error.c +++ b/deps/uv/test/test-tcp-write-error.c @@ -35,10 +35,15 @@ static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size); static uv_tcp_t tcp_server; static uv_tcp_t tcp_client; static uv_tcp_t tcp_peer; /* client socket as accept()-ed by server */ -static uv_write_t write_req; static uv_connect_t connect_req; static int write_cb_called; +static int write_cb_error_called; + +typedef struct { + uv_write_t req; + uv_buf_t buf; +} write_req_t; static void connection_cb(uv_stream_t* server, int status) { @@ -75,40 +80,47 @@ static void connect_cb(uv_connect_t* req, int status) { size_t size; char* data; int r; + write_req_t* wr; ASSERT(req == &connect_req); ASSERT(status == 0); - size = 10*1024*1024; - data = malloc(size); - ASSERT(data != NULL); - - memset(data, '$', size); - buf = uv_buf_init(data, size); - - write_req.data = data; - - r = uv_write(&write_req, req->handle, &buf, 1, write_cb); - ASSERT(r == 0); - - /* Write queue should have been updated. */ - ASSERT(req->handle->write_queue_size > 0); - - /* write_queue_size <= size, part may have already been written. */ - ASSERT(req->handle->write_queue_size <= size); + while (1) { + size = 10 * 1024 * 1024; + data = malloc(size); + ASSERT(data != NULL); + + memset(data, '$', size); + buf = uv_buf_init(data, size); + + wr = (write_req_t*) malloc(sizeof *wr); + wr->buf = buf; + wr->req.data = data; + + r = uv_write(&(wr->req), req->handle, &wr->buf, 1, write_cb); + ASSERT(r == 0); + + if (req->handle->write_queue_size > 0) { + break; + } + } } static void write_cb(uv_write_t* req, int status) { - ASSERT(req == &write_req); - ASSERT(status == -1); + write_req_t* wr; + wr = (write_req_t*) req; - /* This is what this test is all about. */ - ASSERT(tcp_client.write_queue_size == 0); + if (status == -1) { + write_cb_error_called++; + } - free(write_req.data); + if (req->handle->write_queue_size == 0) { + uv_close((uv_handle_t*)&tcp_client, NULL); + } - uv_close((uv_handle_t*)&tcp_client, NULL); + free(wr->buf.base); + free(wr); write_cb_called++; } @@ -148,7 +160,9 @@ TEST_IMPL(tcp_write_error) { r = uv_run(loop); ASSERT(r == 0); - ASSERT(write_cb_called == 1); + ASSERT(write_cb_called > 0); + ASSERT(write_cb_error_called == 1); + ASSERT(tcp_client.write_queue_size == 0); return 0; } diff --git a/deps/uv/test/test-timer.c b/deps/uv/test/test-timer.c index 17bcb84b77..87235a51bc 100644 --- a/deps/uv/test/test-timer.c +++ b/deps/uv/test/test-timer.c @@ -130,3 +130,43 @@ TEST_IMPL(timer) { return 0; } + + +TEST_IMPL(timer_ref) { + uv_timer_t never; + int r; + + /* A timer just initialized should count as one reference. */ + r = uv_timer_init(uv_default_loop(), &never); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +} + + +TEST_IMPL(timer_ref2) { + uv_timer_t never; + int r; + + /* A timer just initialized should count as one reference. */ + r = uv_timer_init(uv_default_loop(), &never); + ASSERT(r == 0); + + /* We start the timer, this should not effect the ref count. */ + r = uv_timer_start(&never, never_cb, 1000, 1000); + ASSERT(r == 0); + + /* One unref should set the loop ref count to zero. */ + uv_unref(uv_default_loop()); + + /* Therefore this does not block */ + uv_run(uv_default_loop()); + + return 0; +} diff --git a/lib/readline.js b/lib/readline.js index 6ced9a573e..2142258109 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -95,12 +95,12 @@ function Interface(input, output, completer) { this.history = []; this.historyIndex = -1; - var winSize = tty.getWindowSize(output.fd); + var winSize = output.getWindowSize(); exports.columns = winSize[1]; if (process.listeners('SIGWINCH').length === 0) { process.on('SIGWINCH', function() { - var winSize = tty.getWindowSize(output.fd); + var winSize = output.getWindowSize(); exports.columns = winSize[1]; }); } diff --git a/lib/repl.js b/lib/repl.js index 23188e2bd3..de380929f5 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -56,11 +56,7 @@ function hasOwnProperty(obj, prop) { var context; -exports.disableColors = true; -if (process.platform != 'win32') { - exports.disableColors = process.env.NODE_DISABLE_COLORS ? true : false; -} - +exports.disableColors = process.env.NODE_DISABLE_COLORS ? true : false; // hack for require.resolve("./relative") to work properly. module.filename = process.cwd() + '/repl'; diff --git a/lib/tty.js b/lib/tty_legacy.js similarity index 100% rename from lib/tty.js rename to lib/tty_legacy.js diff --git a/lib/tty_uv.js b/lib/tty_uv.js new file mode 100644 index 0000000000..89fe28b9eb --- /dev/null +++ b/lib/tty_uv.js @@ -0,0 +1,375 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var assert = require('assert'); +var inherits = require('util').inherits; +var net = require('net'); +var TTY = process.binding('tty_wrap').TTY; +var isTTY = process.binding('tty_wrap').isTTY; + +var stdinHandle; + + +exports.isatty = function(fd) { + return isTTY(fd); +}; + + +exports.setRawMode = function(flag) { + assert.ok(stdinHandle, "stdin must be initialized before calling setRawMode"); + stdinHandle.setRawMode(flag); +}; + + +exports.getWindowSize = function() { + //throw new Error("implement me"); + return 80; +}; + + +exports.setWindowSize = function() { + throw new Error("implement me"); +}; + + +function ReadStream(fd) { + if (!(this instanceof ReadStream)) return new ReadStream(fd); + net.Socket.call(this, { + handle: new TTY(fd) + }); + + this.writable = false; + + var self = this, + keypressListeners = this.listeners('keypress'); + + function onData(b) { + if (keypressListeners.length) { + self._emitKey(b); + } else { + // Nobody's watching anyway + self.removeListener('data', onData); + self.on('newListener', onNewListener); + } + } + + function onNewListener(event) { + if (event == 'keypress') { + self.on('data', onData); + self.removeListener('newListener', onNewListener); + } + } + + if (!stdinHandle) stdinHandle = this._handle; + + this.on('newListener', onNewListener); +} +inherits(ReadStream, net.Socket); +exports.ReadStream = ReadStream; + + +ReadStream.prototype.isTTY = true; + + +/* + Some patterns seen in terminal key escape codes, derived from combos seen + at http://www.midnight-commander.org/browser/lib/tty/key.c + + ESC letter + ESC [ letter + ESC [ modifier letter + ESC [ 1 ; modifier letter + ESC [ num char + ESC [ num ; modifier char + ESC O letter + ESC O modifier letter + ESC O 1 ; modifier letter + ESC N letter + ESC [ [ num ; modifier char + ESC [ [ 1 ; modifier letter + ESC ESC [ num char + ESC ESC O letter + + - char is usually ~ but $ and ^ also happen with rxvt + - modifier is 1 + + (shift * 1) + + (left_alt * 2) + + (ctrl * 4) + + (right_alt * 8) + - two leading ESCs apparently mean the same as one leading ESC +*/ + + +// Regexes used for ansi escape code splitting +var metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/; +var functionKeyCodeRe = + /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/; + + +ReadStream.prototype._emitKey = function(s) { + var char, + key = { + name: undefined, + ctrl: false, + meta: false, + shift: false + }, + parts; + + if (Buffer.isBuffer(s)) { + if (s[0] > 127 && s[1] === undefined) { + s[0] -= 128; + s = '\x1b' + s.toString(this.encoding || 'utf-8'); + } else { + s = s.toString(this.encoding || 'utf-8'); + } + } + + if (s === '\r' || s === '\n') { + // enter + key.name = 'enter'; + + } else if (s === '\t') { + // tab + key.name = 'tab'; + + } else if (s === '\b' || s === '\x7f' || + s === '\x1b\x7f' || s === '\x1b\b') { + // backspace or ctrl+h + key.name = 'backspace'; + key.meta = (s.charAt(0) === '\x1b'); + + } else if (s === '\x1b' || s === '\x1b\x1b') { + // escape key + key.name = 'escape'; + key.meta = (s.length === 2); + + } else if (s === ' ' || s === '\x1b ') { + key.name = 'space'; + key.meta = (s.length === 2); + + } else if (s <= '\x1a') { + // ctrl+letter + key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1); + key.ctrl = true; + + } else if (s.length === 1 && s >= 'a' && s <= 'z') { + // lowercase letter + key.name = s; + + } else if (s.length === 1 && s >= 'A' && s <= 'Z') { + // shift+letter + key.name = s.toLowerCase(); + key.shift = true; + + } else if (parts = metaKeyCodeRe.exec(s)) { + // meta+character key + key.name = parts[1].toLowerCase(); + key.meta = true; + key.shift = /^[A-Z]$/.test(parts[1]); + + } else if (parts = functionKeyCodeRe.exec(s)) { + // ansi escape sequence + + // reassemble the key code leaving out leading \x1b's, + // the modifier key bitflag and any meaningless "1;" sequence + var code = (parts[1] || '') + (parts[2] || '') + + (parts[4] || '') + (parts[6] || ''), + modifier = (parts[3] || parts[5] || 1) - 1; + + // Parse the key modifier + key.ctrl = !!(modifier & 4); + key.meta = !!(modifier & 10); + key.shift = !!(modifier & 1); + + // Parse the key itself + switch (code) { + /* xterm/gnome ESC O letter */ + case 'OP': key.name = 'f1'; break; + case 'OQ': key.name = 'f2'; break; + case 'OR': key.name = 'f3'; break; + case 'OS': key.name = 'f4'; break; + + /* xterm/rxvt ESC [ number ~ */ + case '[11~': key.name = 'f1'; break; + case '[12~': key.name = 'f2'; break; + case '[13~': key.name = 'f3'; break; + case '[14~': key.name = 'f4'; break; + + /* common */ + case '[15~': key.name = 'f5'; break; + case '[17~': key.name = 'f6'; break; + case '[18~': key.name = 'f7'; break; + case '[19~': key.name = 'f8'; break; + case '[20~': key.name = 'f9'; break; + case '[21~': key.name = 'f10'; break; + case '[23~': key.name = 'f11'; break; + case '[24~': key.name = 'f12'; break; + + /* xterm ESC [ letter */ + case '[A': key.name = 'up'; break; + case '[B': key.name = 'down'; break; + case '[C': key.name = 'right'; break; + case '[D': key.name = 'left'; break; + case '[E': key.name = 'clear'; break; + case '[F': key.name = 'end'; break; + case '[H': key.name = 'home'; break; + + /* xterm/gnome ESC O letter */ + case 'OA': key.name = 'up'; break; + case 'OB': key.name = 'down'; break; + case 'OC': key.name = 'right'; break; + case 'OD': key.name = 'left'; break; + case 'OE': key.name = 'clear'; break; + case 'OF': key.name = 'end'; break; + case 'OH': key.name = 'home'; break; + + /* xterm/rxvt ESC [ number ~ */ + case '[1~': key.name = 'home'; break; + case '[2~': key.name = 'insert'; break; + case '[3~': key.name = 'delete'; break; + case '[4~': key.name = 'end'; break; + case '[5~': key.name = 'pageup'; break; + case '[6~': key.name = 'pagedown'; break; + + /* putty */ + case '[[5~': key.name = 'pageup'; break; + case '[[6~': key.name = 'pagedown'; break; + + /* rxvt */ + case '[7~': key.name = 'home'; break; + case '[8~': key.name = 'end'; break; + + /* rxvt keys with modifiers */ + case '[a': key.name = 'up'; key.shift = true; break; + case '[b': key.name = 'down'; key.shift = true; break; + case '[c': key.name = 'right'; key.shift = true; break; + case '[d': key.name = 'left'; key.shift = true; break; + case '[e': key.name = 'clear'; key.shift = true; break; + + case '[2$': key.name = 'insert'; key.shift = true; break; + case '[3$': key.name = 'delete'; key.shift = true; break; + case '[5$': key.name = 'pageup'; key.shift = true; break; + case '[6$': key.name = 'pagedown'; key.shift = true; break; + case '[7$': key.name = 'home'; key.shift = true; break; + case '[8$': key.name = 'end'; key.shift = true; break; + + case 'Oa': key.name = 'up'; key.ctrl = true; break; + case 'Ob': key.name = 'down'; key.ctrl = true; break; + case 'Oc': key.name = 'right'; key.ctrl = true; break; + case 'Od': key.name = 'left'; key.ctrl = true; break; + case 'Oe': key.name = 'clear'; key.ctrl = true; break; + + case '[2^': key.name = 'insert'; key.ctrl = true; break; + case '[3^': key.name = 'delete'; key.ctrl = true; break; + case '[5^': key.name = 'pageup'; key.ctrl = true; break; + case '[6^': key.name = 'pagedown'; key.ctrl = true; break; + case '[7^': key.name = 'home'; key.ctrl = true; break; + case '[8^': key.name = 'end'; key.ctrl = true; break; + + /* misc. */ + case '[Z': key.name = 'tab'; key.shift = true; break; + + } + } else if (s.length > 1 && s[0] !== '\x1b') { + // Got a longer-than-one string of characters. + // Probably a paste, since it wasn't a control sequence. + Array.prototype.forEach.call(s, this._emitKey, this); + return; + } + + // Don't emit a key if no name was found + if (key.name === undefined) { + key = undefined; + } + + if (s.length === 1) { + char = s; + } + + if (key || char) { + this.emit('keypress', char, key); + } +}; + + +function WriteStream(fd) { + if (!(this instanceof WriteStream)) return new WriteStream(fd); + net.Socket.call(this, { + handle: new TTY(fd) + }); + + this.readable = false; +} +inherits(WriteStream, net.Socket); +exports.WriteStream = WriteStream; + + +WriteStream.prototype.isTTY = true; + + +WriteStream.prototype.cursorTo = function(x, y) { + if (typeof x !== 'number' && typeof y !== 'number') + return; + + if (typeof x !== 'number') + throw new Error("Can't set cursor row without also setting it's column"); + + if (typeof y !== 'number') { + this.write('\x1b[' + (x + 1) + 'G'); + } else { + this.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H'); + } +}; + + +WriteStream.prototype.moveCursor = function(dx, dy) { + if (dx < 0) { + this.write('\x1b[' + (-dx) + 'D'); + } else if (dx > 0) { + this.write('\x1b[' + dx + 'C'); + } + + if (dy < 0) { + this.write('\x1b[' + (-dy) + 'A'); + } else if (dy > 0) { + this.write('\x1b[' + dy + 'B'); + } +}; + + +WriteStream.prototype.clearLine = function(dir) { + if (dir < 0) { + // to the beginning + this.write('\x1b[1K'); + } else if (dir > 0) { + // to the end + this.write('\x1b[0K'); + } else { + // entire line + this.write('\x1b[2K'); + } +}; + + +WriteStream.prototype.getWindowSize = function() { + return this._handle.getWindowSize(); +}; diff --git a/node.gyp b/node.gyp index 611e4d65e9..5fe87dec1b 100644 --- a/node.gyp +++ b/node.gyp @@ -43,8 +43,9 @@ 'lib/timers_legacy.js', 'lib/timers_uv.js', 'lib/tls.js', - 'lib/tty.js', + 'lib/tty_legacy.js', 'lib/tty_posix.js', + 'lib/tty_uv.js', 'lib/tty_win32.js', 'lib/url.js', 'lib/util.js', diff --git a/src/node.js b/src/node.js index 761f53609d..bc4bd56c60 100644 --- a/src/node.js +++ b/src/node.js @@ -108,10 +108,10 @@ } else { var binding = process.binding('stdio'); - var fd = binding.openStdin(); var Module = NativeModule.require('module'); - if (NativeModule.require('tty').isatty(fd)) { + // If stdin is a TTY. + if (NativeModule.require('tty').isatty(0)) { // REPL Module.requireRepl().start(); @@ -223,43 +223,54 @@ process.__defineGetter__('stdout', function() { if (stdout) return stdout; - var binding = process.binding('stdio'), - fd = binding.stdoutFD; + var tty_wrap = process.binding('tty_wrap'); + var binding = process.binding('stdio'); + var fd = 1; // Note stdout._type is used for test-module-load-list.js - if (binding.isatty(fd)) { - var tty = NativeModule.require('tty'); - stdout = new tty.WriteStream(fd); - stdout._type = "tty"; - - // FIXME Hack to have stdout not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - binding.unref(); - stdout.on('close', function() { - binding.ref(); - }); - } else if (binding.isStdoutBlocking()) { - var fs = NativeModule.require('fs'); - stdout = new fs.WriteStream(null, {fd: fd}); - stdout._type = "fs"; - } else { - var net = NativeModule.require('net'); - stdout = new net.Stream(fd); - - // FIXME Hack to have stdout not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - binding.unref(); - stdout.on('close', function() { - binding.ref(); - }); - - // FIXME Should probably have an option in net.Stream to create a - // stream from an existing fd which is writable only. But for now - // we'll just add this hack and set the `readable` member to false. - // Test: ./node test/fixtures/echo.js < /etc/passwd - stdout.readable = false; - stdout._type = "pipe"; + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + var tty = NativeModule.require('tty'); + stdout = new tty.WriteStream(fd); + stdout._type = "tty"; + + // FIXME Hack to have stdout not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + binding.unref(); + stdout.on('close', function() { + binding.ref(); + }); + break; + + case 'FILE': + var fs = NativeModule.require('fs'); + stdout = new fs.WriteStream(null, {fd: fd}); + stdout._type = "fs"; + break; + + case 'PIPE': + var net = NativeModule.require('net'); + stdout = new net.Stream(fd); + + // FIXME Should probably have an option in net.Stream to create a + // stream from an existing fd which is writable only. But for now + // we'll just add this hack and set the `readable` member to false. + // Test: ./node test/fixtures/echo.js < /etc/passwd + stdout.readable = false; + stdout._type = "pipe"; + + // FIXME Hack to have stdout not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + binding.unref(); + stdout.on('close', function() { + binding.ref(); + }); + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error("Implement me. Unknown stdout file type!"); } // For supporting legacy API we put the FD here. @@ -280,19 +291,30 @@ process.__defineGetter__('stdin', function() { if (stdin) return stdin; - var binding = process.binding('stdio'), - fd = binding.openStdin(); - - if (binding.isatty(fd)) { - var tty = NativeModule.require('tty'); - stdin = new tty.ReadStream(fd); - } else if (binding.isStdinBlocking()) { - var fs = NativeModule.require('fs'); - stdin = new fs.ReadStream(null, {fd: fd}); - } else { - var net = NativeModule.require('net'); - stdin = new net.Stream(fd); - stdin.readable = true; + var tty_wrap = process.binding('tty_wrap'); + var binding = process.binding('stdio'); + var fd = 0; + + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + var tty = NativeModule.require('tty'); + stdin = new tty.ReadStream(fd); + break; + + case 'FILE': + var fs = NativeModule.require('fs'); + stdin = new fs.ReadStream(null, {fd: fd}); + break; + + case 'PIPE': + var net = NativeModule.require('net'); + stdin = new net.Stream(fd); + stdin.readable = true; + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error("Implement me. Unknown stdin file type!"); } // For supporting legacy API we put the FD here. @@ -447,6 +469,9 @@ case 'net': return process.features.uv ? 'net_uv' : 'net_legacy'; + case 'tty': + return process.features.uv ? 'tty_uv' : 'tty_legacy'; + case 'child_process': return process.features.uv ? 'child_process_uv' : 'child_process_legacy'; diff --git a/src/tty_wrap.cc b/src/tty_wrap.cc index f78739808d..e172965136 100644 --- a/src/tty_wrap.cc +++ b/src/tty_wrap.cc @@ -21,6 +21,16 @@ using v8::Arguments; using v8::Integer; using v8::Undefined; +#define UNWRAP \ + assert(!args.Holder().IsEmpty()); \ + assert(args.Holder()->InternalFieldCount() > 0); \ + TTYWrap* wrap = \ + static_cast(args.Holder()->GetPointerFromInternalField(0)); \ + if (!wrap) { \ + SetErrno(UV_EBADF); \ + return scope.Close(Integer::New(-1)); \ + } + class TTYWrap : StreamWrap { public: @@ -40,12 +50,39 @@ class TTYWrap : StreamWrap { NODE_SET_PROTOTYPE_METHOD(t, "readStop", StreamWrap::ReadStop); NODE_SET_PROTOTYPE_METHOD(t, "write", StreamWrap::Write); + NODE_SET_PROTOTYPE_METHOD(t, "getWindowSize", TTYWrap::GetWindowSize); + NODE_SET_PROTOTYPE_METHOD(t, "setRawMode", SetRawMode); + NODE_SET_METHOD(target, "isTTY", IsTTY); + NODE_SET_METHOD(target, "guessHandleType", GuessHandleType); target->Set(String::NewSymbol("TTY"), t->GetFunction()); } private: + static Handle GuessHandleType(const Arguments& args) { + HandleScope scope; + int fd = args[0]->Int32Value(); + assert(fd >= 0); + + uv_handle_type t = uv_guess_handle(fd); + + switch (t) { + case UV_TTY: + return scope.Close(String::New("TTY")); + + case UV_NAMED_PIPE: + return scope.Close(String::New("PIPE")); + + case UV_FILE: + return scope.Close(String::New("FILE")); + + default: + assert(0); + return v8::Undefined(); + } + } + static Handle IsTTY(const Arguments& args) { HandleScope scope; int fd = args[0]->Int32Value(); @@ -53,6 +90,40 @@ class TTYWrap : StreamWrap { return uv_guess_handle(fd) == UV_TTY ? v8::True() : v8::False(); } + static Handle GetWindowSize(const Arguments& args) { + HandleScope scope; + + UNWRAP + + int width, height; + int r = uv_tty_get_winsize(&wrap->handle_, &width, &height); + + if (r) { + SetErrno(uv_last_error(uv_default_loop()).code); + return v8::Undefined(); + } + + Local a = v8::Array::New(2); + a->Set(0, Integer::New(width)); + a->Set(1, Integer::New(height)); + + return scope.Close(a); + } + + static Handle SetRawMode(const Arguments& args) { + HandleScope scope; + + UNWRAP + + int r = uv_tty_set_mode(&wrap->handle_, args[0]->IsTrue()); + + if (r) { + SetErrno(uv_last_error(uv_default_loop()).code); + } + + return scope.Close(Integer::New(r)); + } + static Handle New(const Arguments& args) { HandleScope scope; diff --git a/test/simple/test-module-load-list.js b/test/simple/test-module-load-list.js index fb2a72ebd6..92f335b901 100644 --- a/test/simple/test-module-load-list.js +++ b/test/simple/test-module-load-list.js @@ -78,25 +78,26 @@ if (!process.features.uv) { case 'fs': expected = expected.concat([ 'NativeModule console', + 'Binding tty_wrap', ]); break; case 'tty': expected = expected.concat([ 'NativeModule console', - 'NativeModule tty', - 'NativeModule tty_posix', + 'Binding tty_wrap', + 'NativeModule tty_uv', 'NativeModule net_uv', 'NativeModule timers_uv', 'Binding timer_wrap', - 'NativeModule _linklist', - 'Binding pipe_wrap', + 'NativeModule _linklist' ]); break; case 'pipe': expected = expected.concat([ 'NativeModule console', + 'Binding tty_wrap', 'NativeModule net_uv', 'NativeModule timers_uv', 'Binding timer_wrap',