Module: Rufio::AnsiLineParser

Defined in:
lib/rufio/ansi_line_parser.rb

Overview

ANSI エスケープコード付きの行をトークン列に分解するモジュール。bat –color=always の出力をシンタックスハイライト表示するために使用する。

Constant Summary collapse

ANSI_SGR_PATTERN =

SGR (Select Graphic Rendition) ANSI エスケープシーケンスにマッチするパターン

/\e\[[0-9;]*m/

Class Method Summary collapse

Class Method Details

.display_width(tokens) ⇒ Integer

トークン配列の表示幅(ANSI コードを除く)を計算する。

Parameters:

  • tokens (Array<Hash>)

    parse が返したトークン配列

Returns:

  • (Integer)

    表示幅



34
35
36
# File 'lib/rufio/ansi_line_parser.rb', line 34

def self.display_width(tokens)
  tokens.sum { |t| TextUtils.display_width(t[:text]) }
end

.parse(line) ⇒ Array<Hash>

ANSI 付き行を String, fg: String|nil のトークン配列に分解する。

Parameters:

  • line (String)

    ANSI コードを含む可能性がある行

Returns:

  • (Array<Hash>)

    fg: のトークン配列



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/rufio/ansi_line_parser.rb', line 14

def self.parse(line)
  tokens = []
  current_fg = nil

  # ANSI SGR シーケンス、非エスケープ文字列、孤立エスケープの順にスキャン
  line.scan(/#{ANSI_SGR_PATTERN}|[^\e]+|\e(?!\[)/) do |part|
    if part.start_with?("\e[")
      current_fg = apply_ansi_sequence(current_fg, part)
    else
      tokens << { text: part, fg: current_fg }
    end
  end

  tokens
end

.wrap(tokens, max_width) ⇒ Array<Array<Hash>>

トークン配列を max_width で折り返し、行ごとのトークン配列の配列を返す。全角文字(日本語等)は幅2として扱う。

Parameters:

  • tokens (Array<Hash>)

    parse が返したトークン配列

  • max_width (Integer)

    折り返し幅(表示幅基準)

Returns:

  • (Array<Array<Hash>>)

    行ごとのトークン配列



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rufio/ansi_line_parser.rb', line 44

def self.wrap(tokens, max_width)
  return [] if tokens.empty? || max_width <= 0

  lines = []
  current_line = []
  current_width = 0

  tokens.each do |token|
    fg = token[:fg]
    current_text = String.new

    token[:text].each_char do |char|
      char_w = TextUtils.char_width(char)

      if current_width + char_w > max_width
        # 折り返し: 現在のテキストをトークンとして確定
        current_line << { text: current_text, fg: fg } unless current_text.empty?
        lines << current_line
        current_line = []
        current_text = String.new
        current_width = 0
      end

      current_text << char
      current_width += char_w
    end

    current_line << { text: current_text, fg: fg } unless current_text.empty?
  end

  lines << current_line unless current_line.empty?
  lines
end