Ruby VT100 转义序列在 Windows 10 上出现不可预测的行为
Unpredictable behavior with Ruby VT100 escape sequences on Windows 10
在 Windows 10 上使用 ruby 2.3.1p112 (2016-04-26 revision 54768) [x64-mingw32]
,版本 10.0.14393
。
先说几件事:
- Windows
echo
命令的行为绕过了 VT100 的控制台模式标志。根据 MSDN,这是正常的,其中标志仅影响 WriteConsole()
和 WriteFile()
.
- 当我使用
SetConsoleMode()
更改标志时,Win32 函数 WriteConsole()
工作正常。它在设置标志时解释 VT100 转义序列。
那么 Ruby 是怎么回事?它以绿色显示红色,并以某种方式忽略了我的控制台标志。还有为什么它显示出更深的绿色?我的理论是,这是 Ruby 以及它如何处理将输出写入控制台的问题。
完整脚本:
#!/usr/bin/ruby
# encoding: UTF-8
require 'rbconfig'
unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
raise 'This script only works on Windows. Quitting.'
end
require 'fiddle'
require 'fiddle/types'
require 'fiddle/import'
class VirtMode
#TODO: Check Windows 10 build number (>= 1511) for mode support
module Kernel32
extend Fiddle::Importer
dlload 'kernel32'
include Fiddle::Win32Types
DWORD_SIZE = sizeof('DWORD')
STD_OUTPUT_HANDLE = -11
STD_INPUT_HANDLE = -10
VIRTUAL_TERMINAL_PROCESSING = 0x0004
extern 'HANDLE GetStdHandle(DWORD)'
extern 'DWORD SetConsoleMode(HANDLE, DWORD)'
extern 'DWORD GetConsoleMode(HANDLE, PDWORD)'
extern 'BOOL WriteConsole(HANDLE, const *char, DWORD, PDWORD, PVOID)'
end
class << self; attr_accessor :stdout, :stdin end
self.stdout = Kernel32::GetStdHandle(Kernel32::STD_OUTPUT_HANDLE)
self.stdin = Kernel32::GetStdHandle(Kernel32::STD_INPUT_HANDLE)
def self.get_mode
mode = [0].pack('L')
success = Kernel32::GetConsoleMode(stdout, mode)
return mode.unpack('L').first if success.nonzero?
raise 'Could not get console mode'
end
def self.enable_virtual_mode
new_mode = get_mode | Kernel32::VIRTUAL_TERMINAL_PROCESSING
#puts new_mode.to_s(2).rjust(32, '0')
return Kernel32::SetConsoleMode(stdout, new_mode).nonzero?
end
def self.disable_virtual_mode
new_mode = get_mode & ~Kernel32::VIRTUAL_TERMINAL_PROCESSING
#puts new_mode.to_s(2).rjust(32, '0')
return Kernel32::SetConsoleMode(stdout, new_mode).nonzero?
end
def self.write_console(text)
written = 0
Kernel32::WriteConsole(stdout, text, text.size, written, 0)
end
end
# It's already disabled but just in case
VirtMode.disable_virtual_mode
puts '--VT100 mode disabled--'
puts "\e[38;2;255;0;32mRuby: Red!\e[0m"
VirtMode.write_console "\e[38;2;255;0;32mWin32: Red!\e[0m\n"
system "echo \e[38;2;255;0;32mEcho: Red!\e[0m\n"
# Now we enable Windows 10 support for VT100
# https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
VirtMode.enable_virtual_mode
puts '--VT100 mode enabled--'
puts "\e[38;2;0;255;32mRuby: Green!\e[0m"
VirtMode.write_console "\e[38;2;0;255;32mWin32: Green!\e[0m\n"
system "echo \e[38;2;0;255;32mEcho: Green!\e[0m\n"
WindowsPowerShell 中的示例输出:
不要在我的 Ruby 中调试此脚本,除非你已经将它输出到 Windows 控制台,例如 PowerShell 或命令提示符,因为 GetConsoleMode()
会失败。
事实证明 Ruby 对 Windows 10 或更高版本下的原生 VT100 的支持已添加到日期为 2016 年 3 月 8 日的 commit 中。在此之前 Ruby 使用自己的VT100 转义序列解析器。
我安装的 Ruby 版本缺少此更改,因此我获取了更新的版本 ruby 2.4.1p111 (2017-03-22 revision 58053) [x64-mingw32]
。
自该提交后,Ruby 现在执行以下操作:
- 如果控制台模式缺少
ENABLE_VIRTUAL_TERMINAL_PROCESSING
标志,则使用自己的 VT100 解析器。
- 如果存在标志,则使用 Windows VT100 支持。
Ruby 将红色显示为绿色的原因可能是 Ruby 的 RGB 颜色代码 VT100 解析器中的错误。例如,"\e[38;2;255;0;32mRuby: Red!\e[0m"
有红色 (255),它实际上将转义序列解释为 "\e[32mRuby: Red!\e[0m"
,即绿色。此错误还解释了启用 VT100 模式时的颜色差异。
使用 Windows VT 模式的 Ruby 2.4 可以正确显示绿色。原来 Ruby 不解释 RGB 颜色代码,因为 RGB 是一些虚拟终端支持的扩展(感谢 Thomas-Dickey)。
在 Windows 10 上使用 ruby 2.3.1p112 (2016-04-26 revision 54768) [x64-mingw32]
,版本 10.0.14393
。
先说几件事:
- Windows
echo
命令的行为绕过了 VT100 的控制台模式标志。根据 MSDN,这是正常的,其中标志仅影响WriteConsole()
和WriteFile()
. - 当我使用
SetConsoleMode()
更改标志时,Win32 函数WriteConsole()
工作正常。它在设置标志时解释 VT100 转义序列。
那么 Ruby 是怎么回事?它以绿色显示红色,并以某种方式忽略了我的控制台标志。还有为什么它显示出更深的绿色?我的理论是,这是 Ruby 以及它如何处理将输出写入控制台的问题。
完整脚本:
#!/usr/bin/ruby
# encoding: UTF-8
require 'rbconfig'
unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
raise 'This script only works on Windows. Quitting.'
end
require 'fiddle'
require 'fiddle/types'
require 'fiddle/import'
class VirtMode
#TODO: Check Windows 10 build number (>= 1511) for mode support
module Kernel32
extend Fiddle::Importer
dlload 'kernel32'
include Fiddle::Win32Types
DWORD_SIZE = sizeof('DWORD')
STD_OUTPUT_HANDLE = -11
STD_INPUT_HANDLE = -10
VIRTUAL_TERMINAL_PROCESSING = 0x0004
extern 'HANDLE GetStdHandle(DWORD)'
extern 'DWORD SetConsoleMode(HANDLE, DWORD)'
extern 'DWORD GetConsoleMode(HANDLE, PDWORD)'
extern 'BOOL WriteConsole(HANDLE, const *char, DWORD, PDWORD, PVOID)'
end
class << self; attr_accessor :stdout, :stdin end
self.stdout = Kernel32::GetStdHandle(Kernel32::STD_OUTPUT_HANDLE)
self.stdin = Kernel32::GetStdHandle(Kernel32::STD_INPUT_HANDLE)
def self.get_mode
mode = [0].pack('L')
success = Kernel32::GetConsoleMode(stdout, mode)
return mode.unpack('L').first if success.nonzero?
raise 'Could not get console mode'
end
def self.enable_virtual_mode
new_mode = get_mode | Kernel32::VIRTUAL_TERMINAL_PROCESSING
#puts new_mode.to_s(2).rjust(32, '0')
return Kernel32::SetConsoleMode(stdout, new_mode).nonzero?
end
def self.disable_virtual_mode
new_mode = get_mode & ~Kernel32::VIRTUAL_TERMINAL_PROCESSING
#puts new_mode.to_s(2).rjust(32, '0')
return Kernel32::SetConsoleMode(stdout, new_mode).nonzero?
end
def self.write_console(text)
written = 0
Kernel32::WriteConsole(stdout, text, text.size, written, 0)
end
end
# It's already disabled but just in case
VirtMode.disable_virtual_mode
puts '--VT100 mode disabled--'
puts "\e[38;2;255;0;32mRuby: Red!\e[0m"
VirtMode.write_console "\e[38;2;255;0;32mWin32: Red!\e[0m\n"
system "echo \e[38;2;255;0;32mEcho: Red!\e[0m\n"
# Now we enable Windows 10 support for VT100
# https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
VirtMode.enable_virtual_mode
puts '--VT100 mode enabled--'
puts "\e[38;2;0;255;32mRuby: Green!\e[0m"
VirtMode.write_console "\e[38;2;0;255;32mWin32: Green!\e[0m\n"
system "echo \e[38;2;0;255;32mEcho: Green!\e[0m\n"
WindowsPowerShell 中的示例输出:
不要在我的 Ruby 中调试此脚本,除非你已经将它输出到 Windows 控制台,例如 PowerShell 或命令提示符,因为 GetConsoleMode()
会失败。
事实证明 Ruby 对 Windows 10 或更高版本下的原生 VT100 的支持已添加到日期为 2016 年 3 月 8 日的 commit 中。在此之前 Ruby 使用自己的VT100 转义序列解析器。
我安装的 Ruby 版本缺少此更改,因此我获取了更新的版本 ruby 2.4.1p111 (2017-03-22 revision 58053) [x64-mingw32]
。
自该提交后,Ruby 现在执行以下操作:
- 如果控制台模式缺少
ENABLE_VIRTUAL_TERMINAL_PROCESSING
标志,则使用自己的 VT100 解析器。 - 如果存在标志,则使用 Windows VT100 支持。
Ruby 将红色显示为绿色的原因可能是 Ruby 的 RGB 颜色代码 VT100 解析器中的错误。例如,"\e[38;2;255;0;32mRuby: Red!\e[0m"
有红色 (255),它实际上将转义序列解释为 "\e[32mRuby: Red!\e[0m"
,即绿色。此错误还解释了启用 VT100 模式时的颜色差异。
使用 Windows VT 模式的 Ruby 2.4 可以正确显示绿色。原来 Ruby 不解释 RGB 颜色代码,因为 RGB 是一些虚拟终端支持的扩展(感谢 Thomas-Dickey)。