diff options
Diffstat (limited to 'ext/io/console/console.c')
-rw-r--r-- | ext/io/console/console.c | 465 |
1 files changed, 304 insertions, 161 deletions
diff --git a/ext/io/console/console.c b/ext/io/console/console.c index 6dadc8d0d6..7130c29a8b 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -2,6 +2,10 @@ /* * console IO module */ + +static const char *const +IO_CONSOLE_VERSION = "0.7.2"; + #include "ruby.h" #include "ruby/io.h" #include "ruby/thread.h" @@ -75,10 +79,10 @@ getattr(int fd, conmode *t) #define SET_LAST_ERROR (0) #endif +#define CSI "\x1b\x5b" + static ID id_getc, id_console, id_close; -#if ENABLE_IO_GETPASS -static ID id_gets, id_chomp_bang; -#endif +static ID id_gets, id_flush, id_chomp_bang; #if defined HAVE_RUBY_FIBER_SCHEDULER_H # include "ruby/fiber/scheduler.h" @@ -898,6 +902,16 @@ console_set_winsize(VALUE io, VALUE size) #endif #ifdef _WIN32 +/* + * call-seq: + * io.check_winsize_changed { ... } -> io + * + * Yields while console input events are queued. + * + * This method is Windows only. + * + * You must require 'io/console' to use this method. + */ static VALUE console_check_winsize_changed(VALUE io) { @@ -984,6 +998,14 @@ console_ioflush(VALUE io) return io; } +/* + * call-seq: + * io.beep + * + * Beeps on the output console. + * + * You must require 'io/console' to use this method. + */ static VALUE console_beep(VALUE io) { @@ -1012,67 +1034,6 @@ mode_in_range(VALUE val, int high, const char *modename) } #if defined _WIN32 -static VALUE -console_goto(VALUE io, VALUE y, VALUE x) -{ - COORD pos; - int fd = GetWriteFD(io); - pos.X = NUM2UINT(x); - pos.Y = NUM2UINT(y); - if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return io; -} - -static VALUE -console_cursor_pos(VALUE io) -{ - rb_console_size_t ws; - int fd = GetWriteFD(io); - if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); -} - -static VALUE -console_move(VALUE io, int y, int x) -{ - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - pos->X += x; - pos->Y += y; - if (!SetConsoleCursorPosition(h, *pos)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return io; -} - -static VALUE -console_goto_column(VALUE io, VALUE val) -{ - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - pos->X = NUM2INT(val); - if (!SetConsoleCursorPosition(h, *pos)) { - rb_syserr_fail(LAST_ERROR, 0); - } - return io; -} - static void constat_clear(HANDLE handle, WORD attr, DWORD len, COORD pos) { @@ -1083,74 +1044,6 @@ constat_clear(HANDLE handle, WORD attr, DWORD len, COORD pos) } static VALUE -console_erase_line(VALUE io, VALUE val) -{ - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - DWORD w; - int mode = mode_in_range(val, 2, "line erase"); - - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - w = winsize_col(&ws); - switch (mode) { - case 0: /* after cursor */ - w -= pos->X; - break; - case 1: /* before *and* cursor */ - w = pos->X + 1; - pos->X = 0; - break; - case 2: /* entire line */ - pos->X = 0; - break; - } - constat_clear(h, ws.wAttributes, w, *pos); - return io; -} - -static VALUE -console_erase_screen(VALUE io, VALUE val) -{ - HANDLE h; - rb_console_size_t ws; - COORD *pos = &ws.dwCursorPosition; - DWORD w; - int mode = mode_in_range(val, 3, "screen erase"); - - h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); - if (!GetConsoleScreenBufferInfo(h, &ws)) { - rb_syserr_fail(LAST_ERROR, 0); - } - w = winsize_col(&ws); - switch (mode) { - case 0: /* erase after cursor */ - w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X); - break; - case 1: /* erase before *and* cursor */ - w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1); - pos->X = 0; - pos->Y = ws.srWindow.Top; - break; - case 2: /* erase entire screen */ - w = (w * winsize_row(&ws)); - pos->X = 0; - pos->Y = ws.srWindow.Top; - break; - case 3: /* erase entire screen */ - w = (w * ws.dwSize.Y); - pos->X = 0; - pos->Y = 0; - break; - } - constat_clear(h, ws.wAttributes, w, *pos); - return io; -} - -static VALUE console_scroll(VALUE io, int line) { HANDLE h; @@ -1180,6 +1073,17 @@ console_scroll(VALUE io, int line) #include "win32_vk.inc" +/* + * call-seq: + * io.pressed?(key) -> bool + * + * Returns +true+ if +key+ is pressed. +key+ may be a virtual key + * code or its name (String or Symbol) with out "VK_" prefix. + * + * This method is Windows only. + * + * You must require 'io/console' to use this method. + */ static VALUE console_key_pressed_p(VALUE io, VALUE k) { @@ -1270,8 +1174,40 @@ console_vt_response(int argc, VALUE *argv, VALUE io, const struct query_args *qa } static VALUE +console_scroll(VALUE io, int line) +{ + if (line) { + VALUE s = rb_sprintf(CSI "%d%c", line < 0 ? -line : line, + line < 0 ? 'T' : 'S'); + rb_io_write(io, s); + } + return io; +} + +# define console_key_pressed_p rb_f_notimplement +#endif + +/* + * call-seq: + * io.cursor -> [row, column] + * + * Returns the current cursor position as a two-element array of integers (row, column) + * + * io.cursor # => [3, 5] + * + * You must require 'io/console' to use this method. + */ +static VALUE console_cursor_pos(VALUE io) { +#ifdef _WIN32 + rb_console_size_t ws; + int fd = GetWriteFD(io); + if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); +#else static const struct query_args query = {"\033[6n", 0}; VALUE resp = console_vt_response(0, 0, io, &query); VALUE row, column, term; @@ -1288,64 +1224,205 @@ console_cursor_pos(VALUE io) RARRAY_ASET(resp, 0, INT2NUM(r)); RARRAY_ASET(resp, 1, INT2NUM(c)); return resp; +#endif } +/* + * call-seq: + * io.goto(line, column) -> io + * + * Set the cursor position at +line+ and +column+. + * + * You must require 'io/console' to use this method. + */ static VALUE console_goto(VALUE io, VALUE y, VALUE x) { - rb_io_write(io, rb_sprintf("\x1b[%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1)); +#ifdef _WIN32 + COORD pos; + int fd = GetWriteFD(io); + pos.X = NUM2UINT(x); + pos.Y = NUM2UINT(y); + if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else + rb_io_write(io, rb_sprintf(CSI "%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1)); +#endif return io; } static VALUE console_move(VALUE io, int y, int x) { +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X += x; + pos->Y += y; + if (!SetConsoleCursorPosition(h, *pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else if (x || y) { VALUE s = rb_str_new_cstr(""); - if (y) rb_str_catf(s, "\x1b[%d%c", y < 0 ? -y : y, y < 0 ? 'A' : 'B'); - if (x) rb_str_catf(s, "\x1b[%d%c", x < 0 ? -x : x, x < 0 ? 'D' : 'C'); + if (y) rb_str_catf(s, CSI "%d%c", y < 0 ? -y : y, y < 0 ? 'A' : 'B'); + if (x) rb_str_catf(s, CSI "%d%c", x < 0 ? -x : x, x < 0 ? 'D' : 'C'); rb_io_write(io, s); rb_io_flush(io); } +#endif return io; } +/* + * call-seq: + * io.goto_column(column) -> io + * + * Set the cursor position at +column+ in the same line of the current + * position. + * + * You must require 'io/console' to use this method. + */ static VALUE console_goto_column(VALUE io, VALUE val) { - rb_io_write(io, rb_sprintf("\x1b[%dG", NUM2UINT(val)+1)); +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X = NUM2INT(val); + if (!SetConsoleCursorPosition(h, *pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else + rb_io_write(io, rb_sprintf(CSI "%dG", NUM2UINT(val)+1)); +#endif return io; } +/* + * call-seq: + * io.erase_line(mode) -> io + * + * Erases the line at the cursor corresponding to +mode+. + * +mode+ may be either: + * 0: after cursor + * 1: before and cursor + * 2: entire line + * + * You must require 'io/console' to use this method. + */ static VALUE console_erase_line(VALUE io, VALUE val) { int mode = mode_in_range(val, 2, "line erase"); - rb_io_write(io, rb_sprintf("\x1b[%dK", mode)); +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + DWORD w; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + w = winsize_col(&ws); + switch (mode) { + case 0: /* after cursor */ + w -= pos->X; + break; + case 1: /* before *and* cursor */ + w = pos->X + 1; + pos->X = 0; + break; + case 2: /* entire line */ + pos->X = 0; + break; + } + constat_clear(h, ws.wAttributes, w, *pos); + return io; +#else + rb_io_write(io, rb_sprintf(CSI "%dK", mode)); +#endif return io; } +/* + * call-seq: + * io.erase_screen(mode) -> io + * + * Erases the screen at the cursor corresponding to +mode+. + * +mode+ may be either: + * 0: after cursor + * 1: before and cursor + * 2: entire screen + * + * You must require 'io/console' to use this method. + */ static VALUE console_erase_screen(VALUE io, VALUE val) { int mode = mode_in_range(val, 3, "screen erase"); - rb_io_write(io, rb_sprintf("\x1b[%dJ", mode)); - return io; -} +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + DWORD w; -static VALUE -console_scroll(VALUE io, int line) -{ - if (line) { - VALUE s = rb_sprintf("\x1b[%d%c", line < 0 ? -line : line, - line < 0 ? 'T' : 'S'); - rb_io_write(io, s); + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); } + w = winsize_col(&ws); + switch (mode) { + case 0: /* erase after cursor */ + w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X); + break; + case 1: /* erase before *and* cursor */ + w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1); + pos->X = 0; + pos->Y = ws.srWindow.Top; + break; + case 2: /* erase entire screen */ + w = (w * winsize_row(&ws)); + pos->X = 0; + pos->Y = ws.srWindow.Top; + break; + case 3: /* erase entire screen */ + w = (w * ws.dwSize.Y); + pos->X = 0; + pos->Y = 0; + break; + } + constat_clear(h, ws.wAttributes, w, *pos); +#else + rb_io_write(io, rb_sprintf(CSI "%dJ", mode)); +#endif return io; } -# define console_key_pressed_p rb_f_notimplement -#endif +/* + * call-seq: + * io.cursor = [line, column] -> io + * + * Same as <tt>io.goto(line, column)</tt> + * + * See IO#goto. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_set(VALUE io, VALUE cpos) { @@ -1354,42 +1431,98 @@ console_cursor_set(VALUE io, VALUE cpos) return console_goto(io, RARRAY_AREF(cpos, 0), RARRAY_AREF(cpos, 1)); } +/* + * call-seq: + * io.cursor_up(n) -> io + * + * Moves the cursor up +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_up(VALUE io, VALUE val) { return console_move(io, -NUM2INT(val), 0); } +/* + * call-seq: + * io.cursor_down(n) -> io + * + * Moves the cursor down +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_down(VALUE io, VALUE val) { return console_move(io, +NUM2INT(val), 0); } +/* + * call-seq: + * io.cursor_left(n) -> io + * + * Moves the cursor left +n+ columns. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_left(VALUE io, VALUE val) { return console_move(io, 0, -NUM2INT(val)); } +/* + * call-seq: + * io.cursor_right(n) -> io + * + * Moves the cursor right +n+ columns. + * + * You must require 'io/console' to use this method. + */ static VALUE console_cursor_right(VALUE io, VALUE val) { return console_move(io, 0, +NUM2INT(val)); } +/* + * call-seq: + * io.scroll_forward(n) -> io + * + * Scrolls the entire scrolls forward +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_scroll_forward(VALUE io, VALUE val) { return console_scroll(io, +NUM2INT(val)); } +/* + * call-seq: + * io.scroll_backward(n) -> io + * + * Scrolls the entire scrolls backward +n+ lines. + * + * You must require 'io/console' to use this method. + */ static VALUE console_scroll_backward(VALUE io, VALUE val) { return console_scroll(io, -NUM2INT(val)); } +/* + * call-seq: + * io.clear_screen -> io + * + * Clears the entire screen and moves the cursor top-left corner. + * + * You must require 'io/console' to use this method. + */ static VALUE console_clear_screen(VALUE io) { @@ -1534,7 +1667,6 @@ io_getch(int argc, VALUE *argv, VALUE io) return rb_funcallv(io, id_getc, argc, argv); } -#if ENABLE_IO_GETPASS static VALUE puts_call(VALUE io) { @@ -1542,6 +1674,12 @@ puts_call(VALUE io) } static VALUE +gets_call(VALUE io) +{ + return rb_funcallv(io, id_gets, 0, 0); +} + +static VALUE getpass_call(VALUE io) { return ttymode(io, rb_io_gets, io, set_noecho, NULL); @@ -1561,7 +1699,8 @@ static VALUE str_chomp(VALUE str) { if (!NIL_P(str)) { - rb_funcallv(str, id_chomp_bang, 0, 0); + const VALUE rs = rb_default_rs; /* rvalue in TruffleRuby */ + rb_funcallv(str, id_chomp_bang, 1, &rs); } return str; } @@ -1578,6 +1717,12 @@ str_chomp(VALUE str) * see String#chomp!. * * You must require 'io/console' to use this method. + * + * require 'io/console' + * IO::console.getpass("Enter password:") + * Enter password: + * # => "mypassword" + * */ static VALUE console_getpass(int argc, VALUE *argv, VALUE io) @@ -1588,6 +1733,7 @@ console_getpass(int argc, VALUE *argv, VALUE io) wio = rb_io_get_write_io(io); if (wio == io && io == rb_stdin) wio = rb_stderr; prompt(argc, argv, wio); + rb_io_flush(wio); str = rb_ensure(getpass_call, io, puts_call, wio); return str_chomp(str); } @@ -1605,11 +1751,10 @@ io_getpass(int argc, VALUE *argv, VALUE io) rb_check_arity(argc, 0, 1); prompt(argc, argv, io); - str = str_chomp(rb_funcallv(io, id_gets, 0, 0)); - puts_call(io); - return str; + rb_check_funcall(io, id_flush, 0, 0); + str = rb_ensure(gets_call, io, puts_call, io); + return str_chomp(str); } -#endif /* * IO console methods @@ -1619,10 +1764,9 @@ Init_console(void) { #undef rb_intern id_getc = rb_intern("getc"); -#if ENABLE_IO_GETPASS id_gets = rb_intern("gets"); + id_flush = rb_intern("flush"); id_chomp_bang = rb_intern("chomp!"); -#endif id_console = rb_intern("console"); id_close = rb_intern("close"); #define init_rawmode_opt_id(name) \ @@ -1670,20 +1814,19 @@ InitVM_console(void) rb_define_method(rb_cIO, "clear_screen", console_clear_screen, 0); rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1); rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0); -#if ENABLE_IO_GETPASS rb_define_method(rb_cIO, "getpass", console_getpass, -1); -#endif rb_define_singleton_method(rb_cIO, "console", console_dev, -1); { + /* :stopdoc: */ VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable"); + /* :startdoc: */ rb_define_method(mReadable, "getch", io_getch, -1); -#if ENABLE_IO_GETPASS rb_define_method(mReadable, "getpass", io_getpass, -1); -#endif } { /* :stopdoc: */ cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject); + rb_define_const(cConmode, "VERSION", rb_str_new_cstr(IO_CONSOLE_VERSION)); rb_define_alloc_func(cConmode, conmode_alloc); rb_undef_method(cConmode, "initialize"); rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1); |