Непредсказуемое поведение escape-последовательностей Ruby VT100 в Windows 10

Использование ruby 2.3.1p112 (2016-04-26 revision 54768) [x64-mingw32] в Windows 10 версии 10.0.14393.

Сначала несколько вещей:

  1. Поведение команды Windows echo обходит флаг режима консоли для VT100. Это нормально в соответствии с MSDN, где флаг влияет только на WriteConsole() и WriteFile().
  2. Функция Win32 WriteConsole() работает правильно, когда я меняю флаги с помощью SetConsoleMode(). Он интерпретирует escape-последовательности VT100, когда установлен флаг.

Так что же происходит с Руби? Он показывает зеленый вместо красного и каким-то образом игнорирует флаги моей консоли. Кроме того, почему он показывает более темный зеленый цвет? Моя теория заключается в том, что это проблема 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"

Пример вывода в Windows PowerShell: введите здесь описание изображения

Не отлаживайте этот скрипт в Rubymine, если он не выводится на консоль Windows, например PowerShell или в командную строку, поскольку GetConsoleMode() завершится ошибкой.


person gavxn    schedule 13.07.2017    source источник


Ответы (1)


Оказывается, поддержка Ruby для собственного VT100 в Windows 10 или более поздней версии была добавлена ​​в коммите от 8 марта 2016 г. До этого Ruby использовал собственный анализатор управляющих последовательностей VT100.

В версии Ruby, которую я установил, не было этого изменения, поэтому я взял более свежую версию, ruby 2.4.1p111 (2017-03-22 revision 58053) [x64-mingw32].

После этого коммита Ruby теперь делает следующее:

  1. Использует собственный синтаксический анализатор VT100, если в консольном режиме отсутствует флаг ENABLE_VIRTUAL_TERMINAL_PROCESSING.
  2. Использует поддержку Windows VT100, если установлен флаг.

Причина, по которой Ruby показывал зеленый вместо красного, скорее всего, связана с ошибкой в ​​синтаксическом анализаторе Ruby VT100 для цветовых кодов RGB. Например, если "\e[38;2;255;0;32mRuby: Red!\e[0m" имеет красный цвет (255), управляющая последовательность фактически интерпретировалась как "\e[32mRuby: Red!\e[0m", то есть зеленого цвета. Эта ошибка также объясняет расхождение цветов при включенном режиме VT100.

Зеленый отображается правильно с Ruby 2.4, который использует режим Windows VT. Оказывается, Ruby не интерпретирует цветовые коды RGB, поскольку RGB — это расширение, поддерживаемое некоторыми виртуальными терминалами (спасибо Thomas-Dickey).

введите здесь описание изображения

person gavxn    schedule 14.07.2017
comment
На самом деле Ruby был прав (для VT100, который никогда не делал RGB). Вопрос о разделителях с запятой, которые вызывали путаницу, подробно обсуждался в других местах (FAQ). - person Thomas Dickey; 13.08.2017
comment
О, я полностью пропустил это. Теперь это имеет смысл. Пытаюсь найти это обсуждение, у вас есть ссылка на часто задаваемые вопросы? - person gavxn; 13.08.2017
comment
Начните здесь и подключитесь к здесь тоже. Несмотря на то, что проблема известна уже около десяти лет, это первый отчет, в котором приложение сломалось. - person Thomas Dickey; 13.08.2017