From f90611ebd69e49ca20732dd6349407a2754c8bed Mon Sep 17 00:00:00 2001 From: Brian Dickens Date: Tue, 10 Mar 2020 16:36:12 -0400 Subject: [PATCH] Commit to C99 or above for smart terminal features With Rebol taking more responsibility for the implementation of the cursoring and per-key terminal functionality of the Windows console, an increasing amount of libRebol code is used to implement the smart console. libRebol is much more readable and manageable when it can use the REBOL_IMPLICIT_END functionality. But that depends on variadic macros, which requires C99 or higher. This commits to the idea that if you want rich console features (like history and tab completion) that the code implementing it is only available in C99 builds or higher. This embraces a general philosophy that while C89 builds are still going to be kept working on Travis for the core, advanced features (ODBC, ZeroMQ, smart console) will use the more streamlined form of libRebol. --- extensions/stdio/p-stdio.c | 11 ++- extensions/stdio/readline-posix.c | 136 +++++++++++----------------- extensions/stdio/readline-windows.c | 97 +++++++++----------- extensions/stdio/readline.h | 30 ++++++ extensions/stdio/stdio-posix.c | 17 +++- extensions/stdio/stdio-windows.c | 88 +++++++++++------- src/mezz/mezz-files.r | 9 +- 7 files changed, 215 insertions(+), 173 deletions(-) diff --git a/extensions/stdio/p-stdio.c b/extensions/stdio/p-stdio.c index 01413ddc29..c5371ee148 100644 --- a/extensions/stdio/p-stdio.c +++ b/extensions/stdio/p-stdio.c @@ -32,8 +32,10 @@ EXTERN_C REBDEV Dev_StdIO; #include "readline.h" -extern STD_TERM *Term_IO; -STD_TERM *Term_IO = nullptr; +#if defined(REBOL_SMART_CONSOLE) + extern STD_TERM *Term_IO; + STD_TERM *Term_IO = nullptr; +#endif // The history mechanism is deliberately separated out from the line-editing // mechanics. The I/O layer is only supposed to emit keystrokes and let the @@ -46,6 +48,7 @@ int Line_History_Index; // Current position in the line history #define Line_Count \ rebUnboxInteger("length of", Line_History, rebEND) +#if defined(REBOL_SMART_CONSOLE) extern REBVAL *Read_Line(STD_TERM *t); @@ -263,6 +266,8 @@ REBVAL *Read_Line(STD_TERM *t) return line; } +#endif // if defined(REBOL_SMART_CONSOLE) + // // Console_Actor: C @@ -310,6 +315,7 @@ REB_R Console_Actor(REBFRM *frame_, REBVAL *port, const REBVAL *verb) if (Req(req)->modes & RDM_NULL) return rebValue("copy #{}", rebEND); + #if defined(REBOL_SMART_CONSOLE) if (Term_IO) { REBVAL *result = Read_Line(Term_IO); if (rebDid("void?", rebQ1(result), rebEND)) { // HALT received @@ -326,6 +332,7 @@ REB_R Console_Actor(REBFRM *frame_, REBVAL *port, const REBVAL *verb) assert(rebDid("text?", result, rebEND)); return rebValue("as binary!", rebR(result), rebEND); } + #endif // !!! A fixed size buffer is used to gather console input. This is // re-used between READ requests. diff --git a/extensions/stdio/readline-posix.c b/extensions/stdio/readline-posix.c index a6689f1771..a4d75b7120 100644 --- a/extensions/stdio/readline-posix.c +++ b/extensions/stdio/readline-posix.c @@ -26,26 +26,27 @@ // parts only for the common standard. // +#include +#include +#include "reb-c.h" + +#include "readline.h" // might define REBOL_SMART_CONSOLE + +#if defined(REBOL_SMART_CONSOLE) + #include #include -#include //for read and write +#include // has POSIX read() and write() #include -#ifndef NO_TTY_ATTRIBUTES - #include -#endif +#include //=//// REBOL INCLUDES + HELPERS //////////////////////////////////////////=// -#include -#include -#include "reb-c.h" - -#include "readline.h" #define xrebWord(cstr) \ - rebValue("lit", cstr, rebEND) + rebValue("lit", cstr) //=//// CONFIGURATION /////////////////////////////////////////////////////=// @@ -82,9 +83,7 @@ struct Reb_Terminal_Struct { // REBVAL *e_pending; - #ifndef NO_TTY_ATTRIBUTES struct termios original_attrs; - #endif }; @@ -94,7 +93,7 @@ static bool Term_Initialized = false; // Terminal init was successful inline static unsigned int Term_End(STD_TERM *t) - { return rebUnboxInteger("length of", t->buffer, rebEND); } + { return rebUnboxInteger("length of", t->buffer); } inline static unsigned int Term_Remain(STD_TERM *t) { return Term_End(t) - t->pos; } @@ -111,20 +110,6 @@ STD_TERM *Init_Terminal(void) { assert(not Term_Initialized); - #ifdef NO_TTY_ATTRIBUTES - // - // !!! The only POSIX platform that did not offer "termios" features was - // the Amiga. But it did its own line editing. - // - // Other platforms for POSIX systems that are old (or embedded) that lack - // termios features is use a plain scanf()/printf() implementation of - // the console, as opposed to complicate this code with #ifdefs. That - // would give the added benefit of being used for C89-only platforms, so - // this code isn't cluttered with rebEND either. - // - return nullptr; - #else - // // Good reference on termios: // // https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios/ @@ -165,10 +150,10 @@ STD_TERM *Init_Terminal(void) // file across sessions. It makes more sense for the logic doing that // to be doing it in Rebol. For starters, we just make it fresh. // - Line_History = rebValue("[{}]", rebEND); // current line is empty string + Line_History = rebValue("[{}]"); // current line is empty string rebUnmanage(Line_History); // allow Line_History to live indefinitely - t->buffer = rebValue("{}", rebEND); + t->buffer = rebValue("{}"); rebUnmanage(t->buffer); t->buf[0] = '\0'; // start read() byte buffer out at empty @@ -179,7 +164,6 @@ STD_TERM *Init_Terminal(void) Term_Initialized = true; return t; - #endif } @@ -204,7 +188,7 @@ int Term_Pos(STD_TERM *t) // REBVAL *Term_Buffer(STD_TERM *t) { - return rebValue("const", t->buffer, rebEND); + return rebValue("const", t->buffer); } @@ -216,9 +200,6 @@ REBVAL *Term_Buffer(STD_TERM *t) // void Quit_Terminal(STD_TERM *t) { - #ifdef NO_TTY_ATTRIBUTES - assert(!"Quit_Terminal called on non-termios build"); - #else assert(Term_Initialized); tcsetattr(0, TCSADRAIN, &t->original_attrs); @@ -230,7 +211,6 @@ void Quit_Terminal(STD_TERM *t) Line_History = nullptr; Term_Initialized = false; - #endif } @@ -294,9 +274,7 @@ void Write_Char(unsigned char c, int n) void Clear_Line_To_End(STD_TERM *t) { int num_codepoints_to_end = Term_Remain(t); - rebElide( - "clear skip", t->buffer, rebI(t->pos), - rebEND); + rebElide("clear skip", t->buffer, rebI(t->pos)); Write_Char(' ', num_codepoints_to_end); // wipe to end of line... Write_Char(BS, num_codepoints_to_end); // ...then return to position @@ -335,8 +313,8 @@ static void Show_Line(STD_TERM *t, int blanks) if (blanks >= 0) { size_t num_bytes; unsigned char *bytes = rebBytes(&num_bytes, - "skip", t->buffer, rebI(t->pos), - rebEND); + "skip", t->buffer, rebI(t->pos) + ); WRITE_UTF8(bytes, num_bytes); rebFree(bytes); @@ -344,8 +322,8 @@ static void Show_Line(STD_TERM *t, int blanks) else { size_t num_bytes; unsigned char *bytes = rebBytes(&num_bytes, - t->buffer, - rebEND); + t->buffer + ); WRITE_UTF8(bytes, num_bytes); rebFree(bytes); @@ -383,9 +361,7 @@ void Delete_Char(STD_TERM *t, bool back) --t->pos; if (end > 0) { - rebElide( - "remove skip", t->buffer, rebI(t->pos), - rebEND); + rebElide("remove skip", t->buffer, rebI(t->pos)); if (back) Write_Char(BS, 1); @@ -422,8 +398,8 @@ void Move_Cursor(STD_TERM *t, int count) if (t->pos < end) { size_t encoded_size; unsigned char *encoded_char = rebBytes(&encoded_size, - "to binary! pick", t->buffer, rebI(t->pos + 1), - rebEND); + "to binary! pick", t->buffer, rebI(t->pos + 1) + ); WRITE_UTF8(encoded_char, encoded_size); rebFree(encoded_char); @@ -459,7 +435,7 @@ REBVAL *Unrecognized_Key_Sequence(STD_TERM *t, int delta) t->buf[0] = '\0'; t->cp = t->buf; - return rebValue("as issue! {[KEY?]}", rebEND); + return rebValue("as issue! {[KEY?]}"); } @@ -481,7 +457,7 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) assert(not e and not t->e_pending); assert( not e_buffered - or (buffered and rebDid("text?", e_buffered, rebEND)) + or (buffered and rebDid("text?", e_buffered)) ); // See notes on why Read_Bytes_Interrupted() can wind up splitting UTF-8 @@ -517,8 +493,8 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) char encoded[4]; int encoded_size = 1 + rebUnboxInteger( "trailing-bytes-for-utf8", - rebR(rebInteger(cast(unsigned char, *t->cp))), - rebEND); + rebR(rebInteger(cast(unsigned char, *t->cp))) + ); assert(encoded_size <= 4); // `cp` can jump back to the beginning of the buffer on each read. @@ -544,13 +520,13 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) REBVAL *char_bin = rebSizedBinary(encoded, encoded_size); if (not buffered) { - e = rebValue("to char!", char_bin, rebEND); + e = rebValue("to char!", char_bin); } else { if (e_buffered) rebElide("append", e_buffered, char_bin); else - e_buffered = rebValue("as text!", char_bin, rebEND); + e_buffered = rebValue("as text!", char_bin); } rebRelease(char_bin); } @@ -671,9 +647,7 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) switch (first) { case 'H': // !!! "home" (in what standard??) #if !defined(NDEBUG) - rebJumps( - "FAIL {ESC H: please report your system info}", - rebEND); + rebJumps("FAIL {ESC H: please report your system info}"); #else e = xrebWord("home"); #endif @@ -681,9 +655,7 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) case 'F': // !!! "end" (in what standard??) #if !defined(NDEBUG) - rebJumps( - "FAIL {ESC F: please report your system info}", - rebEND); + rebJumps("FAIL {ESC F: please report your system info}"); #else e = xrebWord("end"); #endif @@ -715,9 +687,7 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) // involved at that level. Using sigaction() on SIGINT and // causing EINTR is how we would like to be triggering HALT. // - rebJumps( - "FAIL {Unexpected literal Ctrl-C in console}", - rebEND); + rebJumps("FAIL {Unexpected literal Ctrl-C in console}"); } else switch (first) { case DEL: // delete (C0) @@ -752,8 +722,8 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) e = rebValue( "as word! unspaced [", "{ctrl-}", rebR(rebChar(first - 1 + 'a')), - "]", - rebEND); + "]" + ); } else e = Unrecognized_Key_Sequence(t, -1); @@ -780,7 +750,7 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) { if (c == BS) { if (t->pos > 0) { - rebElide("remove skip", t->buffer, rebI(t->pos), rebEND); + rebElide("remove skip", t->buffer, rebI(t->pos)); --t->pos; Write_Char(BS, 1); } @@ -794,7 +764,7 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) // LF *key* as input needs to copy the buffer content out before it // decides to ask for the LF to be output visually. // - rebElide("clear", t->buffer, rebEND); + rebElide("clear", t->buffer); t->pos = 0; Write_Char(LF, 1); } @@ -804,8 +774,8 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) size_t encoded_size; unsigned char *encoded = rebBytes(&encoded_size, "insert skip", t->buffer, rebI(t->pos), codepoint, - codepoint, // fold returning of codepoint in with insertion - rebEND); + codepoint // fold returning of codepoint in with insertion + ); WRITE_UTF8(encoded, encoded_size); rebFree(encoded); @@ -825,14 +795,14 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) // its logic regarding cursor position, newlines, backspacing. // void Term_Insert(STD_TERM *t, const REBVAL *v) { - if (rebDid("char?", v, rebEND)) { - Term_Insert_Char(t, rebUnboxChar(v, rebEND)); + if (rebDid("char?", v)) { + Term_Insert_Char(t, rebUnboxChar(v)); return; } - int len = rebUnboxInteger("length of", v, rebEND); + int len = rebUnboxInteger("length of", v); - if (rebDid("find", v, "backspace", rebEND)) { + if (rebDid("find", v, "backspace")) { // // !!! The logic for backspace and how it interacts is nit-picky, // and "reaches out" to possibly edit the existing buffer. There's @@ -841,7 +811,7 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { // int i; for (i = 1; i <= len; ++i) - Term_Insert_Char(t, rebUnboxChar("pick", v, rebI(i), rebEND)); + Term_Insert_Char(t, rebUnboxChar("pick", v, rebI(i))); } else { // Finesse by doing one big write // @@ -851,13 +821,13 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { REBVAL *v_no_tab = rebValue( "if find", v, "tab [", "replace/all copy", v, "tab", "{ }" - "]", - rebEND); + "]" + ); size_t encoded_size; unsigned char *encoded = rebBytes(&encoded_size, - v_no_tab ? v_no_tab : v, - rebEND); + v_no_tab ? v_no_tab : v + ); rebRelease(v_no_tab); // null-tolerant @@ -867,15 +837,13 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { WRITE_UTF8(encoded, encoded_size); rebFree(encoded); - REBVAL *v_last_line = rebValue( - "next try find-last", v, "newline", - rebEND); + REBVAL *v_last_line = rebValue("next try find-last", v, "newline"); // If there were any newlines, then whatever is in the current line // buffer will no longer be there. // if (v_last_line) { - rebElide("clear", t->buffer, rebEND); + rebElide("clear", t->buffer); t->pos = 0; } @@ -883,8 +851,8 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { t->pos += rebUnboxInteger( "insert skip", t->buffer, rebI(t->pos), insertion, - "length of", insertion, - rebEND); + "length of", insertion + ); rebRelease(v_last_line); // null-tolerant } @@ -903,3 +871,5 @@ void Term_Beep(STD_TERM *t) UNUSED(t); Write_Char(BEL, 1); } + +#endif // end guard against readline in pre-C99 compilers (would need rebEND) diff --git a/extensions/stdio/readline-windows.c b/extensions/stdio/readline-windows.c index bdddd5d0e8..09e7211f09 100644 --- a/extensions/stdio/readline-windows.c +++ b/extensions/stdio/readline-windows.c @@ -26,24 +26,25 @@ // parts only for the common standard. // +#include +#include +#include "reb-c.h" + +#include "readline.h" // will define REBOL_SMART_CONSOLE (if not C89) + +#if defined(REBOL_SMART_CONSOLE) + #include #include #define WIN32_LEAN_AND_MEAN // trim down the Win32 headers #include -#undef IS_ERROR //=//// REBOL INCLUDES + HELPERS //////////////////////////////////////////=// -#include -#include -#include "reb-c.h" - -#include "readline.h" - #define xrebWord(cstr) \ - rebValue("lit", cstr, rebEND) + rebValue("lit", cstr) //=//// CONFIGURATION /////////////////////////////////////////////////////=// @@ -143,7 +144,7 @@ static bool Term_Initialized = false; // Terminal init was successful inline static unsigned int Term_End(STD_TERM *t) - { return rebUnboxInteger("length of", t->buffer, rebEND); } + { return rebUnboxInteger("length of", t->buffer); } inline static unsigned int Term_Remain(STD_TERM *t) { return Term_End(t) - t->pos; } @@ -205,7 +206,7 @@ STD_TERM *Init_Terminal(void) t->original_mode = mode; - t->buffer = rebValue("{}", rebEND); + t->buffer = rebValue("{}"); rebUnmanage(t->buffer); t->in = t->in_tail = t->buf; // start read() byte buffer out at empty @@ -231,7 +232,7 @@ STD_TERM *Init_Terminal(void) // file across sessions. It makes more sense for the logic doing that // to be doing it in Rebol. For starters, we just make it fresh. // - Line_History = rebValue("[{}]", rebEND); // current line is empty string + Line_History = rebValue("[{}]"); // current line is empty string rebUnmanage(Line_History); // allow Line_History to live indefinitely Term_Initialized = true; @@ -260,7 +261,7 @@ int Term_Pos(STD_TERM *t) // REBVAL *Term_Buffer(STD_TERM *t) { - return rebValue("const", t->buffer, rebEND); + return rebValue("const", t->buffer); } @@ -378,9 +379,7 @@ void Write_Char(uint32_t c, int n) void Clear_Line_To_End(STD_TERM *t) { int num_codepoints_to_end = Term_Remain(t); - rebElide( - "clear skip", t->buffer, rebI(t->pos), - rebEND); + rebElide("clear skip", t->buffer, rebI(t->pos)); Write_Char(' ', num_codepoints_to_end); // wipe to end of line... Write_Char(BS, num_codepoints_to_end); // ...then return to position @@ -419,8 +418,8 @@ static void Show_Line(STD_TERM *t, int blanks) if (blanks >= 0) { size_t num_bytes; unsigned char *bytes = rebBytes(&num_bytes, - "skip", t->buffer, rebI(t->pos), - rebEND); + "skip", t->buffer, rebI(t->pos) + ); WRITE_UTF8(bytes, num_bytes); rebFree(bytes); @@ -428,8 +427,8 @@ static void Show_Line(STD_TERM *t, int blanks) else { size_t num_bytes; unsigned char *bytes = rebBytes(&num_bytes, - t->buffer, - rebEND); + t->buffer + ); WRITE_UTF8(bytes, num_bytes); rebFree(bytes); @@ -467,9 +466,7 @@ void Delete_Char(STD_TERM *t, bool back) --t->pos; if (end > 0) { - rebElide( - "remove skip", t->buffer, rebI(t->pos), - rebEND); + rebElide("remove skip", t->buffer, rebI(t->pos)); if (back) Write_Char(BS, 1); @@ -506,8 +503,8 @@ void Move_Cursor(STD_TERM *t, int count) if (t->pos < end) { size_t encoded_size; unsigned char *encoded_char = rebBytes(&encoded_size, - "to binary! pick", t->buffer, rebI(t->pos + 1), - rebEND); + "to binary! pick", t->buffer, rebI(t->pos + 1) + ); WRITE_UTF8(encoded_char, encoded_size); rebFree(encoded_char); @@ -535,7 +532,7 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) assert(not e and not t->e_pending); assert( not e_buffered - or (buffered and rebDid("text?", e_buffered, rebEND)) + or (buffered and rebDid("text?", e_buffered)) ); if (t->in == t->in_tail) { // no residual events from prior read @@ -618,13 +615,9 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) } else { if (e_buffered) - rebElide( - "append", e_buffered, rebR(rebChar(codepoint)), - rebEND); + rebElide("append", e_buffered, rebR(rebChar(codepoint))); else - e_buffered = rebValue( - "to text!", rebR(rebChar(codepoint)), - rebEND); + e_buffered = rebValue("to text!", rebR(rebChar(codepoint))); } } else if ( @@ -678,8 +671,8 @@ REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered) e = rebValue( "as word! unspaced [", "{ctrl-}", rebR(rebChar(wchar - 1 + 'a')), - "]", - rebEND); + "]" + ); } assert(t->in->Event.KeyEvent.wRepeatCount > 0); @@ -715,7 +708,7 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) { if (c == BS) { if (t->pos > 0) { - rebElide("remove skip", t->buffer, rebI(t->pos), rebEND); + rebElide("remove skip", t->buffer, rebI(t->pos)); --t->pos; Write_Char(BS, 1); } @@ -729,7 +722,7 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) // LF *key* as input needs to copy the buffer content out before it // decides to ask for the LF to be output visually. // - rebElide("clear", t->buffer, rebEND); + rebElide("clear", t->buffer); t->pos = 0; Write_Char(LF, 1); } @@ -739,8 +732,8 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) size_t encoded_size; unsigned char *encoded = rebBytes(&encoded_size, "insert skip", t->buffer, rebI(t->pos), codepoint, - codepoint, // fold returning of codepoint in with insertion - rebEND); + codepoint // fold returning of codepoint in with insertion + ); WRITE_UTF8(encoded, encoded_size); rebFree(encoded); @@ -760,14 +753,14 @@ static void Term_Insert_Char(STD_TERM *t, uint32_t c) // its logic regarding cursor position, newlines, backspacing. // void Term_Insert(STD_TERM *t, const REBVAL *v) { - if (rebDid("char?", v, rebEND)) { - Term_Insert_Char(t, rebUnboxChar(v, rebEND)); + if (rebDid("char?", v)) { + Term_Insert_Char(t, rebUnboxChar(v)); return; } - int len = rebUnboxInteger("length of", v, rebEND); + int len = rebUnboxInteger("length of", v); - if (rebDid("find", v, "backspace", rebEND)) { + if (rebDid("find", v, "backspace")) { // // !!! The logic for backspace and how it interacts is nit-picky, // and "reaches out" to possibly edit the existing buffer. There's @@ -776,7 +769,7 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { // int i; for (i = 1; i <= len; ++i) - Term_Insert_Char(t, rebUnboxChar("pick", v, rebI(i), rebEND)); + Term_Insert_Char(t, rebUnboxChar("pick", v, rebI(i))); } else { // Finesse by doing one big write // @@ -786,13 +779,13 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { REBVAL *v_no_tab = rebValue( "if find", v, "tab [", "replace/all copy", v, "tab", "{ }" - "]", - rebEND); + "]" + ); size_t encoded_size; unsigned char *encoded = rebBytes(&encoded_size, - v_no_tab ? v_no_tab : v, - rebEND); + v_no_tab ? v_no_tab : v + ); rebRelease(v_no_tab); // null-tolerant @@ -802,15 +795,13 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { WRITE_UTF8(encoded, encoded_size); rebFree(encoded); - REBVAL *v_last_line = rebValue( - "next try find-last", v, "newline", - rebEND); + REBVAL *v_last_line = rebValue("next try find-last", v, "newline"); // If there were any newlines, then whatever is in the current line // buffer will no longer be there. // if (v_last_line) { - rebElide("clear", t->buffer, rebEND); + rebElide("clear", t->buffer); t->pos = 0; } @@ -818,8 +809,8 @@ void Term_Insert(STD_TERM *t, const REBVAL *v) { t->pos += rebUnboxInteger( "insert skip", t->buffer, rebI(t->pos), insertion, - "length of", insertion, - rebEND); + "length of", insertion + ); rebRelease(v_last_line); // null-tolerant } @@ -838,3 +829,5 @@ void Term_Beep(STD_TERM *t) UNUSED(t); Write_Char(BEL, 1); } + +#endif // end guard against readline in pre-C99 compilers (would need rebEND) diff --git a/extensions/stdio/readline.h b/extensions/stdio/readline.h index 4cebc8fbba..bd374b40b5 100644 --- a/extensions/stdio/readline.h +++ b/extensions/stdio/readline.h @@ -31,6 +31,33 @@ // Windows and POSIX smart consoles. // + +// !!! Note: %reb-c.h must be included prior to this for proper CPLUSPLUS_11 +// detection (MSVC doesn't do this correctly). We do not do it here because +// of lack of include guards, but maybe we should put include guards in? +// +/* #include %reb-c.h */ + + +// If a POSIX system does not offer Termios features, we assume it might also +// be old enough (or simple/embedded) to not support C99 and variadic macros. +// Without variadic macros, we have to use REBOL_EXPLICIT_END and it makes the +// code less pleasant. We tie the two things together to say that there is +// no concern for a "smart-terminal-based C89 build"...if you can only build +// with C89 then another aspect of that constraint is that you aren't going +// to get features like command history or tab completion. +// +#if defined (__STDC_VERSION__) && __STDC_VERSION__ < 199901L + // ... +#elif !defined(CPLUSPLUS_11) + // ...C++11 standardized variadic macros in sync with C99's version... +#elif !defined(TO_WINDOWS) and defined(NO_TTY_ATTRIBUTES) + // ...couldn't do terminal code even if we bothered with C89 support... +#else +// ...good enough to use both REBOL_IMPLICIT_END and terminal functions... + +#define REBOL_SMART_CONSOLE + #include "rebol.h" // !!! The history mechanism will be disconnected from the line editing @@ -89,3 +116,6 @@ extern void Quit_Terminal(STD_TERM *t); // This is at the concept stage at the moment. // extern REBVAL *Try_Get_One_Console_Event(STD_TERM *t, bool buffered); + + +#endif // end guard against readline in pre-C99 compilers (would need rebEND) diff --git a/extensions/stdio/stdio-posix.c b/extensions/stdio/stdio-posix.c index f2a0a9f8af..801ecae2cb 100644 --- a/extensions/stdio/stdio-posix.c +++ b/extensions/stdio/stdio-posix.c @@ -38,22 +38,25 @@ #include #include "readline.h" -extern REBVAL *Read_Line(STD_TERM *t); // Temporary globals: (either move or remove?!) static int Std_Inp = STDIN_FILENO; static int Std_Out = STDOUT_FILENO; -extern STD_TERM *Term_IO; +#if defined(REBOL_SMART_CONSOLE) + extern STD_TERM *Term_IO; +#endif static void Close_Stdio(void) { + #if defined(REBOL_SMART_CONSOLE) if (Term_IO) { Quit_Terminal(Term_IO); Term_IO = nullptr; } + #endif } @@ -90,8 +93,10 @@ DEVICE_CMD Open_IO(REBREQ *io) if (not (req->modes & RDM_NULL)) { + #if defined(REBOL_SMART_CONSOLE) if (isatty(Std_Inp)) // is termios-capable (not redirected to a file) Term_IO = Init_Terminal(); + #endif } else dev->flags |= SF_DEV_NULL; @@ -137,6 +142,7 @@ DEVICE_CMD Write_IO(REBREQ *io) } if (Std_Out >= 0) { + #if defined(REBOL_SMART_CONSOLE) if (Term_IO) { // // We need to sync the cursor position with writes. This means @@ -157,7 +163,9 @@ DEVICE_CMD Write_IO(REBREQ *io) Term_Insert(Term_IO, text); rebRelease(text); } - else { + else + #endif + { long total = write(Std_Out, req->common.data, req->length); if (total < 0) @@ -200,7 +208,10 @@ DEVICE_CMD Read_IO(REBREQ *io) // Null redirection (should be handled at PORT! level to not ask for read) // assert(not (req->modes & RDM_NULL)); + + #if defined(REBOL_SMART_CONSOLE) assert(not Term_IO); // should have handled in %p-stdio.h + #endif req->actual = 0; diff --git a/extensions/stdio/stdio-windows.c b/extensions/stdio/stdio-windows.c index 0eae907ac2..4468b9a204 100644 --- a/extensions/stdio/stdio-windows.c +++ b/extensions/stdio/stdio-windows.c @@ -37,7 +37,10 @@ #include "sys-core.h" #include "readline.h" -extern STD_TERM *Term_IO; + +#if defined(REBOL_SMART_CONSOLE) + extern STD_TERM *Term_IO; +#endif EXTERN_C REBDEV Dev_StdIO; @@ -73,6 +76,12 @@ DEVICE_CMD Quit_IO(REBREQ *dr) { REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy above + #if defined(REBOL_SMART_CONSOLE) + if (Term_IO) + Quit_Terminal(Term_IO); + Term_IO = nullptr; + #endif + Close_Stdio(); dev->flags &= ~RDF_OPEN; return DR_DONE; @@ -116,8 +125,10 @@ DEVICE_CMD Open_IO(REBREQ *io) ); } + #if defined(REBOL_SMART_CONSOLE) if (not Redir_Inp) Term_IO = Init_Terminal(); + #endif } else dev->flags |= SF_DEV_NULL; @@ -165,37 +176,8 @@ DEVICE_CMD Write_IO(REBREQ *io) if (Stdout_Handle == nullptr) return DR_DONE; - if (Redir_Out) { - if (req->modes & RFM_TEXT) { - // - // Writing UTF-8 text. Currently no actual check is done to make - // sure that it's valid UTF-8, even invalid bytes would be written - // but this could be changed. - } - - // !!! Historically, Rebol on Windows automatically "enlined" strings - // on write to turn LF to CR LF. This was done in Prin_OS_String(). - // However, the current idea is to be more prescriptive and not - // support this without a special codec. In lieu of a more efficient - // codec method, those wishing to get CR LF will need to manually - // enline, or ADAPT their WRITE to do this automatically. - // - // Note that redirection on Windows does not use UTF-16 typically. - // Even CMD.EXE requires a /U switch to do so. - - DWORD total_bytes; - BOOL ok = WriteFile( - Stdout_Handle, - req->common.data, - req->length, - &total_bytes, - 0 - ); - if (not ok) - rebFail_OS (GetLastError()); - UNUSED(total_bytes); - } - else { // not redirected, so being sent to the console + #if defined(REBOL_SMART_CONSOLE) + if (Term_IO) { if (req->modes & RFM_TEXT) { // // !!! This is a wasteful step as the text initially came from @@ -258,6 +240,48 @@ DEVICE_CMD Write_IO(REBREQ *io) UNUSED(total_wide_chars); } } + else + #endif + { + // !!! The concept of building C89 on Windows would require us to + // still go through a UTF-16 conversion process to write to the + // console if we were to write to the terminal...even though we would + // not have the rich line editing. Rather than fixing this, it + // would be better to just go through printf()...thus having a generic + // answer for C89 builds on arbitrarily limited platforms, vs. + // catering to it here. + // + assert(Redir_Inp); + + if (req->modes & RFM_TEXT) { + // + // Writing UTF-8 text. Currently no actual check is done to make + // sure that it's valid UTF-8, even invalid bytes would be written + // but this could be changed. + } + + // !!! Historically, Rebol on Windows automatically "enlined" strings + // on write to turn LF to CR LF. This was done in Prin_OS_String(). + // However, the current idea is to be more prescriptive and not + // support this without a special codec. In lieu of a more efficient + // codec method, those wishing to get CR LF will need to manually + // enline, or ADAPT their WRITE to do this automatically. + // + // Note that redirection on Windows does not use UTF-16 typically. + // Even CMD.EXE requires a /U switch to do so. + + DWORD total_bytes; + BOOL ok = WriteFile( + Stdout_Handle, + req->common.data, + req->length, + &total_bytes, + 0 + ); + if (not ok) + rebFail_OS (GetLastError()); + UNUSED(total_bytes); + } req->actual = req->length; // want byte count written, assume success diff --git a/src/mezz/mezz-files.r b/src/mezz/mezz-files.r index 43b5c44eef..ace106327a 100644 --- a/src/mezz/mezz-files.r +++ b/src/mezz/mezz-files.r @@ -114,8 +114,15 @@ read-stdin: function [ return null ] - line: to-text data + line: as text! data ; The data that comes from READ is mutable + + ; !!! Protocol-wise, at the C level stdio implementations often have a + ; newline given back as part of the result. It's not clear if READ with + ; a pure stdio should do this or not, but currently it does. When you + ; use the smart terminal code it doesn't. This should be standardized. + ; trim/with line newline + line ]