feat: update console output method
- remove the macros disable the warning and error of std functions in MSVC because YYCC has disable them in header. - update console input output functions. provide CSharp-like interface for UTF8 console input output. - console output function is done by WriteConsoleW and WriteFile. - console input function still work in progress. - rename console ASCII color macros - add console ASCII color test. - remove EnableUTF8Console because no longer needed. - add a bunch of annotation to describe YYCC UTF8 console strategy. - add UNICODE macro in CMakeLists.txt to order CMake generate Visual Studio solution with UNICODE charset enabled, not MBCS.
This commit is contained in:
parent
019034a9c2
commit
1e990b74ae
|
@ -41,11 +41,11 @@ PROPERTIES
|
|||
CXX_STANDARD_REQUIRED 17
|
||||
CXX_EXTENSION OFF
|
||||
)
|
||||
# Disable MSVC standard library warnings
|
||||
# Order Unicode charset for private using
|
||||
target_compile_definitions(YYCCommonplace
|
||||
PUBLIC
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_CRT_NONSTDC_NO_DEPRECATE>
|
||||
PRIVATE
|
||||
$<$<CXX_COMPILER_ID:MSVC>:UNICODE>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_UNICODE>
|
||||
)
|
||||
# Order build as UTF-8 in MSVC
|
||||
target_compile_options(YYCCommonplace
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
bool EnsureTerminalColor(FILE* fs) {
|
||||
static bool RawEnableColorfulConsole(FILE* fs) {
|
||||
if (!_isatty(_fileno(fs))) return false;
|
||||
|
||||
HANDLE h_output;
|
||||
|
@ -21,35 +21,83 @@ namespace YYCC::ConsoleHelper {
|
|||
|
||||
h_output = (HANDLE)_get_osfhandle(_fileno(fs));
|
||||
if (!GetConsoleMode(h_output, &dw_mode)) return false;
|
||||
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return false;
|
||||
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EnsureTerminalUTF8(FILE* fs) {
|
||||
if (!SetConsoleCP(CP_UTF8)) return false;
|
||||
if (!SetConsoleOutputCP(CP_UTF8)) return false;
|
||||
|
||||
/*_setmode(_fileno(stdout), _O_U8TEXT);*/
|
||||
int _ = _setmode(_fileno(fs), _O_U8TEXT);
|
||||
bool EnableColorfulConsole(FILE* fs) {
|
||||
if (!RawEnableColorfulConsole(stdout)) return false;
|
||||
if (!RawEnableColorfulConsole(stderr)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FGets(std::string& u8_buf, FILE* stream) {
|
||||
std::wstring wcmd;
|
||||
if (std::getline(std::wcin, wcmd).fail()) return false;
|
||||
YYCC::EncodingHelper::WcharToChar(wcmd.c_str(), u8_buf, CP_UTF8);
|
||||
return true;
|
||||
std::string ReadLine() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
return std::string();
|
||||
|
||||
#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 FPuts(const char* u8_buf, FILE* stream) {
|
||||
std::fputws(YYCC::EncodingHelper::UTF8ToWchar(u8_buf).c_str(), stream);
|
||||
void Write(const char* u8_strl) {}
|
||||
|
||||
static void PlainWrite(const std::string& strl) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// fetch stdout handle first
|
||||
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
// if stdout was redirected, this handle may point to a file handle or anything else,
|
||||
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
|
||||
DWORD console_mode;
|
||||
if (GetConsoleMode(hstdout, &console_mode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl.c_str()));
|
||||
size_t wstrl_size = wstrl.size();
|
||||
// write string with size check
|
||||
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteConsoleW(hstdout, wstrl.c_str(), static_cast<DWORD>(wstrl_size), NULL, NULL);
|
||||
}
|
||||
} else {
|
||||
// anything else, use WriteFile instead.
|
||||
// WriteFile do not need extra convertion, because it is direct writing.
|
||||
// check whether string length is overflow
|
||||
size_t strl_size = strl.size() * sizeof(std::string::value_type);
|
||||
// write string with size check
|
||||
if (strl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteFile(hstdout, strl.c_str(), static_cast<DWORD>(strl_size), NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void FPrintf(FILE* stream, const char* u8_fmt, ...) {
|
||||
#elif YYCC_OS == YYCC_OS_LINUX
|
||||
|
||||
// in linux, directly use C function to write.
|
||||
std::fputs(strl.c_str(), stdout);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void Write(const char* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
FPuts(YYCC::StringHelper::VPrintf(u8_fmt, argptr).c_str(), stream);
|
||||
PlainWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void WriteLine(const char* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
std::string cache(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
|
||||
cache += "\n";
|
||||
PlainWrite(cache);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,63 +7,92 @@
|
|||
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
#define YYCC_TERMCOLHDR_BLACK "\033[30m"
|
||||
#define YYCC_TERMCOLHDR_RED "\033[31m"
|
||||
#define YYCC_TERMCOLHDR_GREEN "\033[32m"
|
||||
#define YYCC_TERMCOLHDR_YELLOW "\033[33m"
|
||||
#define YYCC_TERMCOLHDR_BLUE "\033[34m"
|
||||
#define YYCC_TERMCOLHDR_MAGENTA "\033[35m"
|
||||
#define YYCC_TERMCOLHDR_CYAN "\033[36m"
|
||||
#define YYCC_TERMCOLHDR_WHITE "\033[37m"
|
||||
#define YYCC_COLORHDR_BLACK "\033[30m"
|
||||
#define YYCC_COLORHDR_RED "\033[31m"
|
||||
#define YYCC_COLORHDR_GREEN "\033[32m"
|
||||
#define YYCC_COLORHDR_YELLOW "\033[33m"
|
||||
#define YYCC_COLORHDR_BLUE "\033[34m"
|
||||
#define YYCC_COLORHDR_MAGENTA "\033[35m"
|
||||
#define YYCC_COLORHDR_CYAN "\033[36m"
|
||||
#define YYCC_COLORHDR_WHITE "\033[37m"
|
||||
|
||||
#define YYCC_TERMCOLHDR_LIGHT_BLACK "\033[90m"
|
||||
#define YYCC_TERMCOLHDR_LIGHT_RED "\033[91m"
|
||||
#define YYCC_TERMCOLHDR_LIGHT_GREEN "\033[92m"
|
||||
#define YYCC_TERMCOLHDR_LIGHT_YELLOW "\033[93m"
|
||||
#define YYCC_TERMCOLHDR_LIGHT_BLUE "\033[94m"
|
||||
#define YYCC_TERMCOLHDR_LIGHT_MAGENTA "\033[95m"
|
||||
#define YYCC_TERMCOLHDR_LIGHT_CYAN "\033[96m"
|
||||
#define YYCC_TERMCOLHDR_LIGHT_WHITE "\033[97m"
|
||||
#define YYCC_COLORHDR_LIGHT_BLACK "\033[90m"
|
||||
#define YYCC_COLORHDR_LIGHT_RED "\033[91m"
|
||||
#define YYCC_COLORHDR_LIGHT_GREEN "\033[92m"
|
||||
#define YYCC_COLORHDR_LIGHT_YELLOW "\033[93m"
|
||||
#define YYCC_COLORHDR_LIGHT_BLUE "\033[94m"
|
||||
#define YYCC_COLORHDR_LIGHT_MAGENTA "\033[95m"
|
||||
#define YYCC_COLORHDR_LIGHT_CYAN "\033[96m"
|
||||
#define YYCC_COLORHDR_LIGHT_WHITE "\033[97m"
|
||||
|
||||
#define YYCC_TERMCOLTAIL "\033[0m"
|
||||
#define YYCC_COLORTAIL "\033[0m"
|
||||
|
||||
|
||||
#define YYCC_TERMCOL_BLACK(T) "\033[30m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_RED(T) "\033[31m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_GREEN(T) "\033[32m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_YELLOW(T) "\033[33m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_BLUE(T) "\033[34m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_MAGENTA(T) "\033[35m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_CYAN(T) "\033[36m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_WHITE(T) "\033[37m" T "\033[0m"
|
||||
#define YYCC_COLOR_BLACK(T) "\033[30m" T "\033[0m"
|
||||
#define YYCC_COLOR_RED(T) "\033[31m" T "\033[0m"
|
||||
#define YYCC_COLOR_GREEN(T) "\033[32m" T "\033[0m"
|
||||
#define YYCC_COLOR_YELLOW(T) "\033[33m" T "\033[0m"
|
||||
#define YYCC_COLOR_BLUE(T) "\033[34m" T "\033[0m"
|
||||
#define YYCC_COLOR_MAGENTA(T) "\033[35m" T "\033[0m"
|
||||
#define YYCC_COLOR_CYAN(T) "\033[36m" T "\033[0m"
|
||||
#define YYCC_COLOR_WHITE(T) "\033[37m" T "\033[0m"
|
||||
|
||||
#define YYCC_TERMCOL_LIGHT_BLACK(T) "\033[90m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_LIGHT_RED(T) "\033[91m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_LIGHT_GREEN(T) "\033[92m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_LIGHT_YELLOW(T) "\033[93m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_LIGHT_BLUE(T) "\033[94m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_LIGHT_CYAN(T) "\033[96m" T "\033[0m"
|
||||
#define YYCC_TERMCOL_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_BLACK(T) "\033[90m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_RED(T) "\033[91m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_GREEN(T) "\033[92m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_YELLOW(T) "\033[93m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_BLUE(T) "\033[94m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_CYAN(T) "\033[96m" T "\033[0m"
|
||||
#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.
|
||||
*/
|
||||
bool EnsureTerminalColor(FILE* fs);
|
||||
/**
|
||||
* @brief Try setting terminal to UTF8 encoding.
|
||||
* @param fs[in] The stream to be set.
|
||||
* @return true if success, otherwise false.
|
||||
* @remarks If you enable UTF8 for a stream, you must use stream functions provided in this namespace to operate that stream,
|
||||
* because after UTF8 modification, some old standard functions are not work and it may takes a little bit performance reduction.
|
||||
*/
|
||||
bool EnsureTerminalUTF8(FILE* fs);
|
||||
bool EnableColorfulConsole(FILE* fs);
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void Write(const char* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* @param u8_fmt
|
||||
* @param
|
||||
*/
|
||||
void WriteLine(const char* u8_fmt, ...);
|
||||
|
||||
bool FGets(std::string& u8_buf, FILE* stream);
|
||||
void FPuts(const char* u8_buf, FILE* stream);
|
||||
void FPrintf(FILE* stream, const char* u8_fmt, ...);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -21,6 +21,12 @@ PROPERTIES
|
|||
CXX_STANDARD_REQUIRED 17
|
||||
CXX_EXTENSION OFF
|
||||
)
|
||||
# Order Unicode charset for private using
|
||||
target_compile_definitions(YYCCTestbench
|
||||
PRIVATE
|
||||
$<$<CXX_COMPILER_ID:MSVC>:UNICODE>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_UNICODE>
|
||||
)
|
||||
# Order build as UTF-8 in MSVC
|
||||
target_compile_options(YYCCTestbench
|
||||
PRIVATE
|
||||
|
|
|
@ -1,22 +1,42 @@
|
|||
#include <YYCCommonplace.hpp>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Testbench {
|
||||
namespace Console = YYCC::ConsoleHelper;
|
||||
|
||||
namespace YYCCTestbench {
|
||||
|
||||
static void Assert(bool condition, const char* description) {
|
||||
if (condition) {
|
||||
YYCC::ConsoleHelper::FPrintf(stdout, YYCC_TERMCOL_LIGHT_GREEN("OK: %s\n"), description);
|
||||
Console::WriteLine(YYCC_COLOR_LIGHT_GREEN("OK: %s"), description);
|
||||
} else {
|
||||
YYCC::ConsoleHelper::FPrintf(stdout, YYCC_TERMCOL_LIGHT_RED("Failed: %s\n"), description);
|
||||
Console::WriteLine(YYCC_COLOR_LIGHT_RED("Failed: %s\n"), description);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void TerminalTestbench() {
|
||||
// UTF8 Test
|
||||
static void ConsoleTestbench() {
|
||||
// Color Test
|
||||
Console::EnableColorfulConsole(stdout);
|
||||
Console::WriteLine("Color Test:");
|
||||
|
||||
// U+2588 is full block
|
||||
#define TEST_MACRO(col) Console::WriteLine("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col );
|
||||
|
||||
TEST_MACRO(BLACK);
|
||||
TEST_MACRO(RED);
|
||||
TEST_MACRO(GREEN);
|
||||
TEST_MACRO(YELLOW);
|
||||
TEST_MACRO(BLUE);
|
||||
TEST_MACRO(MAGENTA);
|
||||
TEST_MACRO(CYAN);
|
||||
TEST_MACRO(WHITE);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// UTF8 Output Test
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
YYCC::ConsoleHelper::EnsureTerminalUTF8(stdout);
|
||||
YYCC::ConsoleHelper::FPuts("UTF8 Test:\n", stdout);
|
||||
//Console::EnableUTF8Console(stdout);
|
||||
Console::WriteLine("UTF8 Output Test:");
|
||||
static std::vector<const char*> c_TestStrings {
|
||||
"\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8", // JAPAN
|
||||
"\u7B80\u4F53\u4E2D\u6587", // CHINA
|
||||
|
@ -32,12 +52,9 @@ namespace Testbench {
|
|||
"\xF0\x9F\x8D\xA3 \xE2\x9C\x96 \xF0\x9F\x8D\xBA", // EMOJI
|
||||
};
|
||||
for (const auto* ptr : c_TestStrings) {
|
||||
YYCC::ConsoleHelper::FPrintf(stdout, "\t%s\n", ptr);
|
||||
Console::WriteLine("\t%s", ptr);
|
||||
}
|
||||
|
||||
// Color Test
|
||||
YYCC::ConsoleHelper::EnsureTerminalColor(stdout);
|
||||
YYCC::ConsoleHelper::FPuts(YYCC_TERMCOL_LIGHT_CYAN("Colorful Terminal\n"), stdout);
|
||||
}
|
||||
|
||||
static void StringTestbench() {
|
||||
|
@ -151,28 +168,28 @@ namespace Testbench {
|
|||
filters.Add("All Files (*.*)", {"*.*"});
|
||||
params.SetDefaultFileTypeIndex(0u);
|
||||
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
|
||||
YYCC::ConsoleHelper::FPrintf(stdout, "Open File: %s\n", ret.c_str());
|
||||
Console::WriteLine("Open File: %s", ret.c_str());
|
||||
}
|
||||
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
|
||||
YYCC::ConsoleHelper::FPuts("Open Multiple Files:\n", stdout);
|
||||
Console::WriteLine("Open Multiple Files:");
|
||||
for (const auto& item : rets) {
|
||||
YYCC::ConsoleHelper::FPrintf(stdout, "\t%s\n", item.c_str());
|
||||
Console::WriteLine("\t%s", item.c_str());
|
||||
}
|
||||
}
|
||||
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
|
||||
YYCC::ConsoleHelper::FPrintf(stdout, "Save File: %s\n", ret.c_str());
|
||||
Console::WriteLine("Save File: %s", ret.c_str());
|
||||
}
|
||||
params.Clear();
|
||||
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
|
||||
YYCC::ConsoleHelper::FPrintf(stdout, "Open Folder: %s\n", ret.c_str());
|
||||
Console::WriteLine("Open Folder: %s", ret.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** args) {
|
||||
Testbench::TerminalTestbench();
|
||||
Testbench::StringTestbench();
|
||||
Testbench::ParserTestbench();
|
||||
//Testbench::DialogTestbench();
|
||||
YYCCTestbench::ConsoleTestbench();
|
||||
//YYCCTestbench::StringTestbench();
|
||||
//YYCCTestbench::ParserTestbench();
|
||||
//YYCCTestbench::DialogTestbench();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user