fix: fix console helper

- add annotations for console helper for better user experience.
- change build macro to allow using console helper in non-Windows platform. because console color macros is universal and should not be limited in Windows platform.
This commit is contained in:
yyc12345 2024-06-11 11:40:09 +08:00
parent 629a608133
commit b6c53ac707
3 changed files with 127 additions and 99 deletions

View File

@ -1,18 +1,23 @@
#include "ConsoleHelper.hpp" #include "ConsoleHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "StringHelper.hpp" #include "StringHelper.hpp"
#include <iostream> #include <iostream>
// Include Windows used headers in Windows.
#if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
#include <Windows.h> #include <Windows.h>
#include <io.h> #include <io.h>
#include <fcntl.h> #include <fcntl.h>
#include "WinImportSuffix.hpp" #include "WinImportSuffix.hpp"
#endif
namespace YYCC::ConsoleHelper { namespace YYCC::ConsoleHelper {
#pragma region Windows Specific Functions
#if YYCC_OS == YYCC_OS_WINDOWS
static bool RawEnableColorfulConsole(FILE* fs) { static bool RawEnableColorfulConsole(FILE* fs) {
if (!_isatty(_fileno(fs))) return false; if (!_isatty(_fileno(fs))) return false;
@ -26,38 +31,30 @@ namespace YYCC::ConsoleHelper {
return true; return true;
} }
bool EnableColorfulConsole(FILE* fs) { /*
if (!RawEnableColorfulConsole(stdout)) return false; Reference:
if (!RawEnableColorfulConsole(stderr)) return false; * https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
return true; * https://stackoverflow.com/questions/69830460/reading-utf-8-input
}
//template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, char> || std::is_same_v<_Ty, wchar_t>, int> = 0> There is 3 way to make Windows console enable UTF8 mode.
//static bool FetchEOL(std::basic_string<_Ty>& internal_buffer, std::basic_string<_Ty>& result_buffer) {
// // try finding EOL in internal buffer
// size_t pos;
// if constexpr (std::is_same_v<_Ty, char>) internal_buffer.find_first_of('\n');
// else internal_buffer.find_first_of(L'\n');
// // check finding result First one is calling SetConsoleCP and SetConsoleOutputCP.
// if (pos == std::wstring::npos) { The side effect of this is std::cin and std::cout is broken,
// // the whole string do not include EOL, fully appended to return value however there is a patch for this issue.
// result_buffer += internal_buffer;
// internal_buffer.clear(); Second one is calling _set_mode with _O_U8TEXT or _O_U16TEXT to enable Unicode mode for Windows console.
// // return false mean need more data This also have side effect which is stronger than first one.
// return false; All puts family functions (ASCII-based output functions) will throw assertion exception.
// } else { You only can use putws family functions (wide-char-based output functions).
// // split result However these functions can not be used without calling _set_mode in Windows design.
// // push into result and remain some in internal buffer.
// result_buffer.append(internal_buffer, 0u, pos); There still is another method, using WriteConsoleW directly visiting console.
// internal_buffer.erase(0u, pos + 1u); // +1 because EOL take one place. This function family can output correct string without calling any extra functions!
// // return true mean success finding This method is what we adopted.
// return true; */
// }
//}
template<bool _bIsConsole> template<bool _bIsConsole>
static std::string PlainRead(HANDLE hStdIn) { static std::string WinConsoleRead(HANDLE hStdIn) {
using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>; using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>;
// Prepare an internal buffer because the read data may not be fully used. // Prepare an internal buffer because the read data may not be fully used.
@ -130,34 +127,7 @@ namespace YYCC::ConsoleHelper {
return real_return_buffer; return real_return_buffer;
} }
std::string ReadLine() { static void WinConsoleWrite(const std::string& strl) {
#if YYCC_OS == YYCC_OS_WINDOWS
// get stdin mode
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
// use different method to get according to whether stdin is redirected
DWORD dwConsoleMode;
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
return PlainRead<true>(hStdIn);
} else {
return PlainRead<false>(hStdIn);
}
#elif YYCC_OS == YYCC_OS_LINUX
// in linux, directly use C++ function to fetch.
std::string cmd;
if (std::getline(std::cin, cmd).fail()) cmd.clear();
return cmd;
#endif
}
void Write(const char* u8_strl) {}
static void PlainWrite(const std::string& strl) {
#if YYCC_OS == YYCC_OS_WINDOWS
// Prepare some Win32 variables // Prepare some Win32 variables
// fetch stdout handle first // fetch stdout handle first
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
@ -185,8 +155,56 @@ namespace YYCC::ConsoleHelper {
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL); WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
} }
} }
}
#elif YYCC_OS == YYCC_OS_LINUX #endif
#pragma endregion
bool EnableColorfulConsole() {
#if YYCC_OS == YYCC_OS_WINDOWS
if (!RawEnableColorfulConsole(stdout)) return false;
if (!RawEnableColorfulConsole(stderr)) return false;
return true;
#else
// just return true and do nothing
return true
#endif
}
std::string ReadLine() {
#if YYCC_OS == YYCC_OS_WINDOWS
// get stdin mode
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
// use different method to get according to whether stdin is redirected
DWORD dwConsoleMode;
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
return WinConsoleRead<true>(hStdIn);
} else {
return WinConsoleRead<false>(hStdIn);
}
#else
// in linux, directly use C++ function to fetch.
std::string cmd;
if (std::getline(std::cin, cmd).fail()) cmd.clear();
return cmd;
#endif
}
static void RawWrite(const std::string& strl) {
#if YYCC_OS == YYCC_OS_WINDOWS
// call Windows specific writer
WinConsoleWrite(strl);
#else
// in linux, directly use C function to write. // in linux, directly use C function to write.
std::fputs(strl.c_str(), stdout); std::fputs(strl.c_str(), stdout);
@ -197,7 +215,7 @@ namespace YYCC::ConsoleHelper {
void Write(const char* u8_fmt, ...) { void Write(const char* u8_fmt, ...) {
va_list argptr; va_list argptr;
va_start(argptr, u8_fmt); va_start(argptr, u8_fmt);
PlainWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr)); RawWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
va_end(argptr); va_end(argptr);
} }
@ -206,10 +224,9 @@ namespace YYCC::ConsoleHelper {
va_start(argptr, u8_fmt); va_start(argptr, u8_fmt);
std::string cache(YYCC::StringHelper::VPrintf(u8_fmt, argptr)); std::string cache(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
cache += "\n"; cache += "\n";
PlainWrite(cache); RawWrite(cache);
va_end(argptr); va_end(argptr);
} }
} }
#endif

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "YYCCInternal.hpp" #include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <cstdio> #include <cstdio>
#include <string> #include <string>
@ -47,52 +46,64 @@ namespace YYCC::ConsoleHelper {
#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m" #define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
/** /**
* @brief Try letting terminal support ASCII color schema. * @brief Enable Windows console color support.
* @param fs[in] The stream to be set. * @details This actually is enable virtual console feature for stdout and stderr.
* @return true if success, otherwise false. * @return True if success, otherwise false.
* @remarks This function only works on Windows and do nothing on other platforms such as Linux,
* because we assume all terminals existing on other platform support color feature as default.
*/ */
bool EnableColorfulConsole(FILE* fs); bool EnableColorfulConsole();
/*
Reference:
* https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
* https://stackoverflow.com/questions/69830460/reading-utf-8-input
There is 3 way to make Windows console enable UTF8 mode.
First one is calling SetConsoleCP and SetConsoleOutputCP.
The side effect of this is std::cin and std::cout is broken,
however there is a patch for this issue.
Second one is calling _set_mode with _O_U8TEXT or _O_U16TEXT to enable Unicode mode for Windows console.
This also have side effect which is stronger than first one.
All puts family functions (ASCII-based output functions) will throw assertion exception.
You only can use putws family functions (wide-char-based output functions).
However these functions can not be used without calling _set_mode in Windows design.
There still is another method, using WriteConsoleW directly visiting console.
This function family can output correct string without calling any extra functions!
This method is what we adopted.
*/
/** /**
* @brief * @brief Universal console read function
* @return * @details This function is more like C# Console.ReadLine().
* It read user input with UTF8 encoding until reaching EOL.
*
* This function provide an universal, platform-independent way to read UTF8 string from console,
* no matter whether it is redirected.
* @return The UTF8 encoded string this function read. EOL is excluded.
* @remarks In Windows, this function will try use native Win32 function for reading,
* because standard C/C++ function can not handle UTF8 input on Windows normally.
* In other platforms, this function will redirect request to std::readline with std::cin,
* which is all programmer commonly used method.
* It also mean that we assume stdin is encoded by UTF8 on these platforms.
*
* Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
* This action is actually remove all CR chars in result string.
*
* This function also can be used as ordering user press Enter key by
* simply calling this function and ignoring its return value.
*/ */
std::string ReadLine(); std::string ReadLine();
/** /**
* @brief * @brief Universal console write function
* @param u8_fmt The format, or a simple string (format related chars still need escape). * @details This function is more like C# Console.Write().
* @param ...[in] The parameter for formatter. * It write user given UTF8 string into console.
*
* This function provide an universal, platform-independent way to write UTF8 string into console,
* no matter whether it is redirected.
* @param u8_fmt[in] The format string.
* If you just want to write a pure string, you should escape formatter chars (%) in this string,
* because this function always take this parameter as a format string.
* @param ...[in] The arguments to be formatted.
* @remarks In Windows, this funcion will use native Win32 function for writing,
* because standard C/C++ function can not handle UTF8 output on Windows normally.
* In other platforms, this function will redirect request to std::fprintf with stdout,
* which is all programmer commonly used method.
* It also mean that we assume stdout is encoded by UTF8 on these platforms.
*/ */
void Write(const char* u8_fmt, ...); void Write(const char* u8_fmt, ...);
/** /**
* @brief * @brief Universal console write function with automatic EOL
* @param u8_fmt * @details This function is same as Write(const char*, ...),
* @param * but it will automatically add EOL in output to break line.
* This is commonly used.
* @param u8_fmt[in] The format string.
* If you just want to write a pure string, you should escape formatter chars (%) in this string,
* because this function always take this parameter as a format string.
* If you want to output plain string, you need escape format
* @param ...[in] The arguments to be formatted.
*/ */
void WriteLine(const char* u8_fmt, ...); void WriteLine(const char* u8_fmt, ...);
} }
#endif

View File

@ -16,7 +16,7 @@ namespace YYCCTestbench {
static void ConsoleTestbench() { static void ConsoleTestbench() {
// Color Test // Color Test
Console::EnableColorfulConsole(stdout); Console::EnableColorfulConsole();
Console::WriteLine("Color Test:"); Console::WriteLine("Color Test:");
#define TEST_MACRO(col) Console::WriteLine("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col ); #define TEST_MACRO(col) Console::WriteLine("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col );