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"
#if YYCC_OS == YYCC_OS_WINDOWS
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
#include <iostream>
// Include Windows used headers in Windows.
#if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <io.h>
#include <fcntl.h>
#include "WinImportSuffix.hpp"
#endif
namespace YYCC::ConsoleHelper {
#pragma region Windows Specific Functions
#if YYCC_OS == YYCC_OS_WINDOWS
static bool RawEnableColorfulConsole(FILE* fs) {
if (!_isatty(_fileno(fs))) return false;
@ -26,38 +31,30 @@ namespace YYCC::ConsoleHelper {
return true;
}
bool EnableColorfulConsole(FILE* fs) {
if (!RawEnableColorfulConsole(stdout)) return false;
if (!RawEnableColorfulConsole(stderr)) return false;
return true;
}
/*
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
//template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, char> || std::is_same_v<_Ty, wchar_t>, int> = 0>
//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');
There is 3 way to make Windows console enable UTF8 mode.
// // check finding result
// if (pos == std::wstring::npos) {
// // the whole string do not include EOL, fully appended to return value
// result_buffer += internal_buffer;
// internal_buffer.clear();
// // return false mean need more data
// return false;
// } else {
// // split result
// // push into result and remain some in internal buffer.
// result_buffer.append(internal_buffer, 0u, pos);
// internal_buffer.erase(0u, pos + 1u); // +1 because EOL take one place.
// // return true mean success finding
// return true;
// }
//}
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.
*/
template<bool _bIsConsole>
static std::string PlainRead(HANDLE hStdIn) {
static std::string WinConsoleRead(HANDLE hStdIn) {
using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>;
// Prepare an internal buffer because the read data may not be fully used.
@ -130,34 +127,7 @@ namespace YYCC::ConsoleHelper {
return real_return_buffer;
}
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 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
static void WinConsoleWrite(const std::string& strl) {
// Prepare some Win32 variables
// fetch stdout handle first
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);
}
}
}
#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.
std::fputs(strl.c_str(), stdout);
@ -197,7 +215,7 @@ namespace YYCC::ConsoleHelper {
void Write(const char* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
PlainWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
RawWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
va_end(argptr);
}
@ -206,10 +224,9 @@ namespace YYCC::ConsoleHelper {
va_start(argptr, u8_fmt);
std::string cache(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
cache += "\n";
PlainWrite(cache);
RawWrite(cache);
va_end(argptr);
}
}
#endif

View File

@ -1,6 +1,5 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <cstdio>
#include <string>
@ -47,52 +46,64 @@ namespace YYCC::ConsoleHelper {
#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
/**
* @brief Try letting terminal support ASCII color schema.
* @param fs[in] The stream to be set.
* @return true if success, otherwise false.
* @brief Enable Windows console color support.
* @details This actually is enable virtual console feature for stdout and stderr.
* @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
* @return
* @brief Universal console read function
* @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();
/**
* @brief
* @param u8_fmt The format, or a simple string (format related chars still need escape).
* @param ...[in] The parameter for formatter.
* @brief Universal console write function
* @details This function is more like C# Console.Write().
* 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, ...);
/**
* @brief
* @param u8_fmt
* @param
* @brief Universal console write function with automatic EOL
* @details This function is same as Write(const char*, ...),
* 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, ...);
}
#endif

View File

@ -16,7 +16,7 @@ namespace YYCCTestbench {
static void ConsoleTestbench() {
// Color Test
Console::EnableColorfulConsole(stdout);
Console::EnableColorfulConsole();
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 );