summaryrefslogtreecommitdiff
path: root/ext/io/console
diff options
context:
space:
mode:
Diffstat (limited to 'ext/io/console')
-rw-r--r--ext/io/console/console.c168
-rw-r--r--ext/io/console/depend1
-rw-r--r--ext/io/console/extconf.rb16
-rw-r--r--ext/io/console/io-console.gemspec3
4 files changed, 157 insertions, 31 deletions
diff --git a/ext/io/console/console.c b/ext/io/console/console.c
index 85e6a0613e..5f3c9478f7 100644
--- a/ext/io/console/console.c
+++ b/ext/io/console/console.c
@@ -4,7 +4,7 @@
*/
static const char *const
-IO_CONSOLE_VERSION = "0.7.2";
+IO_CONSOLE_VERSION = "0.8.2";
#include "ruby.h"
#include "ruby/io.h"
@@ -84,6 +84,11 @@ getattr(int fd, conmode *t)
static ID id_getc, id_close;
static ID id_gets, id_flush, id_chomp_bang;
+#ifndef HAVE_RB_INTERNED_STR_CSTR
+# define rb_str_to_interned_str(str) rb_str_freeze(str)
+# define rb_interned_str_cstr(str) rb_str_freeze(rb_usascii_str_new_cstr(str))
+#endif
+
#if defined HAVE_RUBY_FIBER_SCHEDULER_H
# include "ruby/fiber/scheduler.h"
#elif defined HAVE_RB_SCHEDULER_TIMEOUT
@@ -125,7 +130,14 @@ io_get_write_io_fallback(VALUE io)
#define rb_io_get_write_io io_get_write_io_fallback
#endif
-#define sys_fail(io) rb_sys_fail_str(rb_io_path(io))
+#ifndef DHAVE_RB_SYSERR_FAIL_STR
+# define rb_syserr_fail_str(e, mesg) rb_exc_raise(rb_syserr_new_str(e, mesg))
+#endif
+
+#define sys_fail(io) do { \
+ int err = errno; \
+ rb_syserr_fail_str(err, rb_io_path(io)); \
+} while (0)
#ifndef HAVE_RB_F_SEND
#ifndef RB_PASS_CALLED_KEYWORDS
@@ -828,6 +840,9 @@ console_winsize(VALUE io)
return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws)));
}
+static VALUE console_scroll(VALUE io, int line);
+static VALUE console_goto(VALUE io, VALUE y, VALUE x);
+
/*
* call-seq:
* io.winsize = [rows, columns]
@@ -844,7 +859,8 @@ console_set_winsize(VALUE io, VALUE size)
#if defined _WIN32
HANDLE wh;
int newrow, newcol;
- BOOL ret;
+ COORD oldsize;
+ SMALL_RECT oldwindow;
#endif
VALUE row, col, xpixel, ypixel;
const VALUE *sz;
@@ -879,18 +895,62 @@ console_set_winsize(VALUE io, VALUE size)
if (!GetConsoleScreenBufferInfo(wh, &ws)) {
rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo");
}
+ oldsize = ws.dwSize;
+ oldwindow = ws.srWindow;
+ if (ws.srWindow.Right + 1 < newcol) {
+ ws.dwSize.X = newcol;
+ }
+ if (ws.dwSize.Y < newrow) {
+ ws.dwSize.Y = newrow;
+ }
+ /* expand screen buffer first if needed */
+ if (!SetConsoleScreenBufferSize(wh, ws.dwSize)) {
+ rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
+ }
+ /* refresh ws for new dwMaximumWindowSize */
+ if (!GetConsoleScreenBufferInfo(wh, &ws)) {
+ rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo");
+ }
+ /* check new size before modifying buffer content */
+ if (newrow <= 0 || newcol <= 0 ||
+ newrow > ws.dwMaximumWindowSize.Y ||
+ newcol > ws.dwMaximumWindowSize.X) {
+ SetConsoleScreenBufferSize(wh, oldsize);
+ /* remove scrollbar if possible */
+ SetConsoleWindowInfo(wh, TRUE, &oldwindow);
+ rb_raise(rb_eArgError, "out of range winsize: [%d, %d]", newrow, newcol);
+ }
+ /* shrink screen buffer width */
ws.dwSize.X = newcol;
- ret = SetConsoleScreenBufferSize(wh, ws.dwSize);
+ /* shrink screen buffer height if window height were the same */
+ if (oldsize.Y == ws.srWindow.Bottom - ws.srWindow.Top + 1) {
+ ws.dwSize.Y = newrow;
+ }
ws.srWindow.Left = 0;
- ws.srWindow.Top = 0;
- ws.srWindow.Right = newcol-1;
- ws.srWindow.Bottom = newrow-1;
+ ws.srWindow.Right = newcol - 1;
+ ws.srWindow.Bottom = ws.srWindow.Top + newrow -1;
+ if (ws.dwCursorPosition.Y > ws.srWindow.Bottom) {
+ console_scroll(io, ws.dwCursorPosition.Y - ws.srWindow.Bottom);
+ ws.dwCursorPosition.Y = ws.srWindow.Bottom;
+ console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X));
+ }
+ if (ws.srWindow.Bottom > ws.dwSize.Y - 1) {
+ console_scroll(io, ws.srWindow.Bottom - (ws.dwSize.Y - 1));
+ ws.dwCursorPosition.Y -= ws.srWindow.Bottom - (ws.dwSize.Y - 1);
+ console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X));
+ ws.srWindow.Bottom = ws.dwSize.Y - 1;
+ }
+ ws.srWindow.Top = ws.srWindow.Bottom - (newrow - 1);
+ /* perform changes to winsize */
if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
- rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo");
+ int last_error = LAST_ERROR;
+ SetConsoleScreenBufferSize(wh, oldsize);
+ rb_syserr_fail(last_error, "SetConsoleWindowInfo");
}
- /* retry when shrinking buffer after shrunk window */
- if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) {
- rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
+ /* perform screen buffer shrinking if necessary */
+ if ((ws.dwSize.Y < oldsize.Y || ws.dwSize.X < oldsize.X) &&
+ !SetConsoleScreenBufferSize(wh, ws.dwSize)) {
+ rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
}
/* remove scrollbar if possible */
if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
@@ -1209,7 +1269,7 @@ console_cursor_pos(VALUE 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));
+ return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y - ws.srWindow.Top), UINT2NUM(ws.dwCursorPosition.X));
#else
static const struct query_args query = {"\033[6n", 0};
VALUE resp = console_vt_response(0, 0, io, &query);
@@ -1242,11 +1302,17 @@ static VALUE
console_goto(VALUE io, VALUE y, VALUE x)
{
#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)) {
+ 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 = NUM2UINT(x);
+ pos->Y = ws.srWindow.Top + NUM2UINT(y);
+ if (!SetConsoleCursorPosition(h, *pos)) {
rb_syserr_fail(LAST_ERROR, 0);
}
#else
@@ -1639,13 +1705,11 @@ console_dev(int argc, VALUE *argv, VALUE klass)
VALUE con = 0;
VALUE sym = 0;
- rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS);
-
if (argc) {
Check_Type(sym = argv[0], T_SYMBOL);
}
- // Force the class to be File.
+ /* Force the class to be File. */
if (klass == rb_cIO) klass = rb_cFile;
if (console_dev_get(klass, &con)) {
@@ -1811,6 +1875,61 @@ io_getpass(int argc, VALUE *argv, VALUE io)
return str_chomp(str);
}
+#if defined(_WIN32) || defined(HAVE_TTYNAME_R) || defined(HAVE_TTYNAME)
+/*
+ * call-seq:
+ * io.ttyname -> string or nil
+ *
+ * Returns name of associated terminal (tty) if +io+ is not a tty.
+ * Returns +nil+ otherwise.
+ */
+static VALUE
+console_ttyname(VALUE io)
+{
+ int fd = rb_io_descriptor(io);
+ if (!isatty(fd)) return Qnil;
+# if defined _WIN32
+ return rb_usascii_str_new_lit("con");
+# elif defined HAVE_TTYNAME_R
+ {
+ char termname[1024], *tn = termname;
+ size_t size = sizeof(termname);
+ int e;
+ if (ttyname_r(fd, tn, size) == 0)
+ return rb_interned_str_cstr(tn);
+ if ((e = errno) == ERANGE) {
+ VALUE s = rb_str_new(0, size);
+ while (1) {
+ tn = RSTRING_PTR(s);
+ size = rb_str_capacity(s);
+ if (ttyname_r(fd, tn, size) == 0) {
+ return rb_str_to_interned_str(rb_str_resize(s, strlen(tn)));
+ }
+ if ((e = errno) != ERANGE) break;
+ if ((size *= 2) >= INT_MAX/2) break;
+ rb_str_resize(s, size);
+ }
+ }
+ rb_syserr_fail_str(e, rb_sprintf("ttyname_r(%d)", fd));
+ UNREACHABLE_RETURN(Qnil);
+ }
+# elif defined HAVE_TTYNAME
+ {
+ const char *tn = ttyname(fd);
+ if (!tn) {
+ int e = errno;
+ rb_syserr_fail_str(e, rb_sprintf("ttyname(%d)", fd));
+ }
+ return rb_interned_str_cstr(tn);
+ }
+# else
+# error No ttyname function
+# endif
+}
+#else
+# define console_ttyname rb_f_notimplement
+#endif
+
/*
* IO console methods
*/
@@ -1878,24 +1997,23 @@ InitVM_console(void)
rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1);
rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0);
rb_define_method(rb_cIO, "getpass", console_getpass, -1);
+ rb_define_method(rb_cIO, "ttyname", console_ttyname, 0);
rb_define_singleton_method(rb_cIO, "console", console_dev, -1);
{
- /* :stopdoc: */
+ /* :nodoc: */
VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable");
- /* :startdoc: */
rb_define_method(mReadable, "getch", io_getch, -1);
rb_define_method(mReadable, "getpass", io_getpass, -1);
}
{
- /* :stopdoc: */
+ /* :nodoc: */
cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject);
- rb_define_const(cConmode, "VERSION", rb_str_new_cstr(IO_CONSOLE_VERSION));
+ rb_define_const(cConmode, "VERSION", rb_obj_freeze(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);
rb_define_method(cConmode, "echo=", conmode_set_echo, 1);
rb_define_method(cConmode, "raw!", conmode_set_raw, -1);
rb_define_method(cConmode, "raw", conmode_raw_new, -1);
- /* :startdoc: */
}
}
diff --git a/ext/io/console/depend b/ext/io/console/depend
index e66b6f4f5d..150a138d4d 100644
--- a/ext/io/console/depend
+++ b/ext/io/console/depend
@@ -139,6 +139,7 @@ console.o: $(hdrdir)/ruby/internal/intern/re.h
console.o: $(hdrdir)/ruby/internal/intern/ruby.h
console.o: $(hdrdir)/ruby/internal/intern/select.h
console.o: $(hdrdir)/ruby/internal/intern/select/largesize.h
+console.o: $(hdrdir)/ruby/internal/intern/set.h
console.o: $(hdrdir)/ruby/internal/intern/signal.h
console.o: $(hdrdir)/ruby/internal/intern/sprintf.h
console.o: $(hdrdir)/ruby/internal/intern/string.h
diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb
index 6161b747b5..dd3d221ae5 100644
--- a/ext/io/console/extconf.rb
+++ b/ext/io/console/extconf.rb
@@ -5,11 +5,16 @@ require 'mkmf'
# See https://bugs.ruby-lang.org/issues/20345
MakeMakefile::RbConfig ||= ::RbConfig
-have_func("rb_io_path")
-have_func("rb_io_descriptor")
-have_func("rb_io_get_write_io")
-have_func("rb_io_closed_p")
-have_func("rb_io_open_descriptor")
+have_func("rb_syserr_fail_str(0, Qnil)") or
+have_func("rb_syserr_new_str(0, Qnil)") or
+ abort
+
+have_func("rb_interned_str_cstr")
+have_func("rb_io_path", "ruby/io.h")
+have_func("rb_io_descriptor", "ruby/io.h")
+have_func("rb_io_get_write_io", "ruby/io.h")
+have_func("rb_io_closed_p", "ruby/io.h")
+have_func("rb_io_open_descriptor", "ruby/io.h")
have_func("rb_ractor_local_storage_value_newkey")
is_wasi = /wasi/ =~ MakeMakefile::RbConfig::CONFIG["platform"]
@@ -47,6 +52,7 @@ when true
elsif have_func("rb_scheduler_timeout") # 3.0
have_func("rb_io_wait")
end
+ have_func("ttyname_r") or have_func("ttyname")
create_makefile("io/console") {|conf|
conf << "\n""VK_HEADER = #{vk_header}\n"
}
diff --git a/ext/io/console/io-console.gemspec b/ext/io/console/io-console.gemspec
index f9c1729cb7..0a19992734 100644
--- a/ext/io/console/io-console.gemspec
+++ b/ext/io/console/io-console.gemspec
@@ -23,7 +23,8 @@ Gem::Specification.new do |s|
s.require_path = %[lib]
s.files = %w[
.document
- LICENSE.txt
+ BSDL
+ COPYING
README.md
ext/io/console/console.c
ext/io/console/extconf.rb