C# 13 - 신규 이스케이프 시퀀스 '\e'
현재 "Language Feature Status"에 있는 목록들 모두 Visual Studio Preview 17.11p3 기준으로 구현이 완료됐고, 서서히 공식 문서에까지 등장하는 상황입니다.
What's new in C# 13
; https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13
그러니 하나씩 살펴볼까요? ^^
우선, 아주 간단해서 정말 알기 쉬운 ^^ 주제를 시작으로 골랐습니다. 기존의 escape sequence에서,
String escape sequences
; https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#string-escape-sequences
새롭게 0x1b 값을 나타내는, 즉 키보드의 ESC 문자에 해당하는 "
\e"가 추가됐습니다.
// (Preview가 아니더라도 최신 버전의) Visual Studio 2022에서도 테스트 가능
// csproj의 <LangVersion>preview</LangVersion>로 설정해야 빌드 가능
char escape_char = '\e';
Debug.Assert(escape_char == (char)0x1b, "...");
Debug.Assert(escape_char == '\u001b', "...");
Debug.Assert(escape_char == '\U0000001b', "...");
Debug.Assert(escape_char == '\x1b', "...");
끝입니다. ^^
문법만 살펴보면 단순한데, 이에 관한 배경 설명은 좀 복잡합니다. 원래 윈도우 운영체제의 명령어 창은 VT sequence를 지원하지 않았습니다.
Karma Karma Karma Karma Console Chameleon
; https://devblogs.microsoft.com/commandline/new-experimental-console-features/#karma-karma-karma-karma-console-chameleon
하지만,
*NIX 환경의 터미널 모드를 지원하는 오픈소스 도구들이 늘어나면서 윈도우 역시 이에 대한 요구가 지속적으로 높아졌다고 합니다. 결국 Windows 10부터 이에 대한 지원이 추가됐고 그 영향이 C# 언어에도 미치게 된 것입니다.
사실 윈도우 10의 변화가 아니었더라도, C#은 .NET Core 이후로 다중 플랫폼을 지원하게 되면서 자연스럽게 *NIX 환경의 터미널 사용을 감안해 이에 대한 편의성 차원에서도 결국
\e를 추가했을 것입니다.
언어에서의 지원과 상관없이 한 가지 유의해야 할 점은,
\e 표현을 주로 사용하게 될 VT 환경은 Windows 10 이후부터, 좀 더 정확히는
devblogs 글에 따르면 1609(Anniversary Update)부터,
Q&A 글에 따르면 1511부터 지원한다는 점입니다.
그냥 간단하게, 최신 업데이트된 Windows 10+ 환경에서 아래의 코드를 수행하면,
Console.WriteLine("일반 텍스트");
Console.WriteLine("\u001b[4m밑줄 텍스트\u001b[0m");
Console.WriteLine("\u001b[38;2;255;0;0m빨간색 텍스트\u001b[0m");
이렇게 텍스트가 출력됩니다.
물론, 신규 C# 문법을 적용하면 아래와 같이 (쬐끔) 더 간단하게 작성할 수 있습니다.
Console.WriteLine("일반 텍스트");
Console.WriteLine("\e[4m밑줄 텍스트\e[0m");
Console.WriteLine("\e[38;2;255;0;0m빨간색 텍스트\e[0m");
참고로, 현재 명령어 창의 환경이 VT-100을 지원하는지 닷넷에서 확인하는 방법은 없는 듯합니다. 대신 리눅스 환경에서는 TERM 환경변수를 통해 알 수 있고, 윈도우 환경에서는 P/Invoke를 이용해 다음과 같이 체크할 수 있습니다.
using System.Diagnostics;
using System.Runtime.InteropServices;
internal class Program
{
const int STD_OUTPUT_HANDLE = -11;
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
static void Main(string[] args)
{
IntPtr pHandle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(pHandle, out uint lpMode);
PrintOutputConsoleMode(lpMode);
}
private static void PrintOutputConsoleMode(uint lpMode)
{
foreach (ConsoleOutputMode mask in Enum.GetValues<ConsoleOutputMode>())
{
if (((ConsoleOutputMode)lpMode).HasFlag(mask))
{
Console.WriteLine(mask);
}
}
}
}
[Flags]
public enum ConsoleOutputMode : uint
{
ENABLE_PROCESSED_OUTPUT = 0x1,
ENABLE_WRAP_AT_EOL_OUTPUT = 0x2,
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4,
DISABLE_NEWLINE_AUTO_RETURN = 0x8,
ENABLE_LVB_GRID_WORLDWIDE = 0x10
}
위 코드를 제 환경(Windows 11)에서 실행하면 다음과 같은 결과를 볼 수 있는데요,
ENABLE_PROCESSED_OUTPUT
ENABLE_WRAP_AT_EOL_OUTPUT
ENABLE_VIRTUAL_TERMINAL_PROCESSING
바로 저 출력에 "ENABLE_VIRTUAL_TERMINAL_PROCESSING" 값이 있으면 VT-100을 지원하는 것입니다. 따라서, 원한다면 이 모드를 끄는 것도 가능합니다. 가령, 다음과 같이 ENABLE_VIRTUAL_TERMINAL_PROCESSING 옵션을 끄면,
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
lpMode ^= (uint)ConsoleOutputMode.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(pHandle, lpMode);
이제부턴 위에서 실습한 Console.WriteLine 결과가 다음과 같은 식으로 출력됩니다.
일반 텍스트
←[4m밑줄 텍스트←[0m
←[38;2;255;0;0m빨간색 텍스트←[0m
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]