diff --git a/doc/site/modules/io/stdin.markdown b/doc/site/modules/io/stdin.markdown index 12938335..0fe02cfb 100644 --- a/doc/site/modules/io/stdin.markdown +++ b/doc/site/modules/io/stdin.markdown @@ -4,6 +4,18 @@ The standard input stream. ## Static Methods +### **isRaw** + +Returns `true` if stdin is in raw mode. When in raw mode, input is not echoed +or buffered, and all characters, even non-printing and control characters go +into stdin. + +Defaults to `false`. + +### **isRaw**=(value) + +Sets raw mode on or off. + ### **readByte**() Reads one byte of input from stdin. Blocks the current fiber until a byte has diff --git a/src/cli/modules.c b/src/cli/modules.c index 6aa28fd4..ed1df04c 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -35,6 +35,8 @@ extern void statSpecialDevice(WrenVM* vm); extern void statUser(WrenVM* vm); extern void statIsDirectory(WrenVM* vm); extern void statIsFile(WrenVM* vm); +extern void stdinIsRaw(WrenVM* vm); +extern void stdinIsRawSet(WrenVM* vm); extern void stdinReadStart(WrenVM* vm); extern void stdinReadStop(WrenVM* vm); extern void schedulerCaptureMethods(WrenVM* vm); @@ -141,6 +143,8 @@ static ModuleRegistry modules[] = METHOD("isFile", statIsFile) END_CLASS CLASS(Stdin) + STATIC_METHOD("isRaw", stdinIsRaw) + STATIC_METHOD("isRaw=(_)", stdinIsRawSet) STATIC_METHOD("readStart_()", stdinReadStart) STATIC_METHOD("readStop_()", stdinReadStop) END_CLASS diff --git a/src/module/io.c b/src/module/io.c index 7c07fdd3..a79b227a 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -46,6 +46,9 @@ static WrenValue* stdinOnData = NULL; // The stream used to read from stdin. Initialized on the first read. static uv_stream_t* stdinStream = NULL; +// True if stdin has been set to raw mode. +static bool isStdinRaw = false; + // Frees all resources related to stdin. static void shutdownStdin() { @@ -67,6 +70,8 @@ static void shutdownStdin() wrenReleaseValue(getVM(), stdinOnData); stdinOnData = NULL; } + + uv_tty_reset_mode(); } void ioShutdown() @@ -487,6 +492,53 @@ void statIsFile(WrenVM* vm) wrenSetSlotBool(vm, 0, S_ISREG(stat->st_mode)); } +// Sets up the stdin stream if not already initialized. +static void initStdin() +{ + if (stdinStream == NULL) + { + if (uv_guess_handle(stdinDescriptor) == UV_TTY) + { + // stdin is connected to a terminal. + uv_tty_t* handle = (uv_tty_t*)malloc(sizeof(uv_tty_t)); + uv_tty_init(getLoop(), handle, stdinDescriptor, true); + + stdinStream = (uv_stream_t*)handle; + } + else + { + // stdin is a pipe or a file. + uv_pipe_t* handle = (uv_pipe_t*)malloc(sizeof(uv_pipe_t)); + uv_pipe_init(getLoop(), handle, false); + uv_pipe_open(handle, stdinDescriptor); + stdinStream = (uv_stream_t*)handle; + } + } +} + +void stdinIsRaw(WrenVM* vm) +{ + wrenSetSlotBool(vm, 0, isStdinRaw); +} + +void stdinIsRawSet(WrenVM* vm) +{ + initStdin(); + + isStdinRaw = wrenGetSlotBool(vm, 1); + + if (uv_guess_handle(stdinDescriptor) == UV_TTY) + { + uv_tty_t* handle = (uv_tty_t*)stdinStream; + uv_tty_set_mode(handle, isStdinRaw ? UV_TTY_MODE_RAW : UV_TTY_MODE_NORMAL); + } + else + { + // Can't set raw mode when not talking to a TTY. + // TODO: Make this a runtime error? + } +} + static void allocCallback(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) { @@ -540,25 +592,7 @@ static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead, void stdinReadStart(WrenVM* vm) { - if (stdinStream == NULL) - { - if (uv_guess_handle(stdinDescriptor) == UV_TTY) - { - // stdin is connected to a terminal. - uv_tty_t* handle = (uv_tty_t*)malloc(sizeof(uv_tty_t)); - uv_tty_init(getLoop(), handle, stdinDescriptor, true); - stdinStream = (uv_stream_t*)handle; - } - else - { - // stdin is a pipe or a file. - uv_pipe_t* handle = (uv_pipe_t*)malloc(sizeof(uv_pipe_t)); - uv_pipe_init(getLoop(), handle, false); - uv_pipe_open(handle, stdinDescriptor); - stdinStream = (uv_stream_t*)handle; - } - } - + initStdin(); uv_read_start(stdinStream, allocCallback, stdinReadCallback); // TODO: Check return. } diff --git a/src/module/io.wren b/src/module/io.wren index 3cf14430..4634ddb7 100644 --- a/src/module/io.wren +++ b/src/module/io.wren @@ -215,6 +215,9 @@ foreign class Stat { } class Stdin { + foreign static isRaw + foreign static isRaw=(value) + static readByte() { return read_ { // Peel off the first byte. diff --git a/src/module/io.wren.inc b/src/module/io.wren.inc index 2e246a32..ea56c0c6 100644 --- a/src/module/io.wren.inc +++ b/src/module/io.wren.inc @@ -217,6 +217,9 @@ static const char* ioModuleSource = "}\n" "\n" "class Stdin {\n" +" foreign static isRaw\n" +" foreign static isRaw=(value)\n" +"\n" " static readByte() {\n" " return read_ {\n" " // Peel off the first byte.\n" diff --git a/test/io/stdin/is_raw.wren b/test/io/stdin/is_raw.wren new file mode 100644 index 00000000..556cbf1d --- /dev/null +++ b/test/io/stdin/is_raw.wren @@ -0,0 +1,28 @@ +import "io" for Stdin + +// Defaults to false. +System.print(Stdin.isRaw) // expect: false + +// stdin: abcdefgh +for (i in 1..4) { + System.print(Stdin.readByte()) +} +// expect: 97 +// expect: 98 +// expect: 99 +// expect: 100 + +Stdin.isRaw = true +System.print(Stdin.isRaw) // expect: true + +for (i in 1..4) { + System.print(Stdin.readByte()) +} +// expect: 101 +// expect: 102 +// expect: 103 +// expect: 104 + +// TODO: This doesn't actually detect a visible difference between raw and +// non-raw mode. Maybe add support to the test runner for writing non-printing +// characters to stdin?