Class: TansParser::State

Inherits:
Object
  • Object
show all
Defined in:
lib/tans_parser/state.rb

Overview

Represents the parsed state of a terminal screen. Provides high-level query methods for AI consumption.

Constant Summary collapse

TEXT_SEARCH_TIMEOUT =

Search for text across the entire terminal. For regex patterns, matching is bounded by a timeout to prevent ReDoS.

5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ State

Returns a new instance of State.

Raises:

  • (ArgumentError)


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

def initialize(data)
  raise ArgumentError, "State data must include :size key" unless data[:size]
  raise ArgumentError, "State data must include :rows key" unless data[:rows]

  @rows = data[:size][:rows]
  @cols = data[:size][:cols]
  @grid = data[:rows]
  @cursor = data[:cursor]

  cursor_info = data[:cursor].is_a?(Hash) ? data[:cursor] : {}
  @cursor_visible = data.key?(:cursor_visible) ? data[:cursor_visible] : (cursor_info[:visible] != false)
  @cursor_style = data.key?(:cursor_style) ? data[:cursor_style] : (cursor_info[:style] || 1)

  @mouse_mode = data[:mouse_mode] || :none
  @mouse_format = data[:mouse_format] || :normal
end

Instance Attribute Details

#colsObject (readonly)

Returns the value of attribute cols.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def cols
  @cols
end

#cursorObject (readonly)

Returns the value of attribute cursor.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def cursor
  @cursor
end

#cursor_styleObject (readonly)

Returns the value of attribute cursor_style.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def cursor_style
  @cursor_style
end

#cursor_visibleObject (readonly)

Returns the value of attribute cursor_visible.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def cursor_visible
  @cursor_visible
end

#gridObject (readonly)

Returns the value of attribute grid.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def grid
  @grid
end

#mouse_formatObject (readonly)

Returns the value of attribute mouse_format.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def mouse_format
  @mouse_format
end

#mouse_modeObject (readonly)

Returns the value of attribute mouse_mode.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def mouse_mode
  @mouse_mode
end

#rowsObject (readonly)

Returns the value of attribute rows.



11
12
13
# File 'lib/tans_parser/state.rb', line 11

def rows
  @rows
end

Instance Method Details

#background_at(row, col) ⇒ Object



81
82
83
84
85
# File 'lib/tans_parser/state.rb', line 81

def background_at(row, col)
  return nil if row >= @rows || col >= @cols

  @grid[row][col][:bg]
end

#find_text(pattern) ⇒ Object



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
# File 'lib/tans_parser/state.rb', line 46

def find_text(pattern)
  results = []
  is_regex = pattern.is_a?(Regexp)

  @grid.each_with_index do |row, ri|
    text = row.map { |c| c[:char] }.join
    pos = 0
    begin
      if is_regex
        Timeout.timeout(TEXT_SEARCH_TIMEOUT) do
          while (match = text.index(pattern, pos))
            results << { row: ri, col: match, text: pattern.to_s, full_line: text }
            pos = match + 1
          end
        end
      else
        while (match = text.index(pattern, pos))
          results << { row: ri, col: match, text: pattern, full_line: text }
          pos = match + 1
        end
      end
    rescue Timeout::Error
      # Stop processing on timeout — return partial results
    end
  end
  results
end

#foreground_at(row, col) ⇒ Object

Get the color at a specific cell



75
76
77
78
79
# File 'lib/tans_parser/state.rb', line 75

def foreground_at(row, col)
  return nil if row >= @rows || col >= @cols

  @grid[row][col][:fg]
end

#plain_textObject

Get plain text of the entire terminal (no ANSI)



31
32
33
# File 'lib/tans_parser/state.rb', line 31

def plain_text
  @grid.map { |row| row.map { |c| c[:char] }.join.rstrip }.join("\n")
end

#style_at(row, col) ⇒ Object



87
88
89
90
91
92
# File 'lib/tans_parser/state.rb', line 87

def style_at(row, col)
  return nil if row >= @rows || col >= @cols

  cell = @grid[row][col]
  { bold: cell[:bold], italic: cell[:italic], underline: cell[:underline] }
end

#text_at(row, col, length = @cols - col) ⇒ Object

Get text at a specific position



36
37
38
39
40
# File 'lib/tans_parser/state.rb', line 36

def text_at(row, col, length = @cols - col)
  return "" if row >= @rows || col >= @cols

  @grid[row][col, length].map { |c| c[:char] }.join
end

#to_ai_jsonObject



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/tans_parser/state.rb', line 94

def to_ai_json
  h = extract_highlights
  cursor_info = @cursor.is_a?(Hash) ? @cursor : {}
  r = cursor_info[:row] || cursor_info["row"] || 0
  c = cursor_info[:col] || cursor_info["col"] || 0
  styled_count = h.count { |hl| hl[:bold] || hl[:italic] || hl[:underline] || hl[:fg] || hl[:bg] }

  summary = "Cursor at [#{r},#{c}]. "
  summary << "#{styled_count} styled row#{"s" unless styled_count == 1}"
  fgs = h.flat_map { |hl| hl[:fg] }.compact.uniq
  bgs = h.flat_map { |hl| hl[:bg] }.compact.uniq
  summary << ", colors: fg=#{fgs.sort.join(",")}" unless fgs.empty?
  summary << ", bg=#{bgs.sort.join(",")}" unless bgs.empty?
  summary << "."

  {
    size: { rows: @rows, cols: @cols },
    cursor: cursor_info,
    text: plain_text,
    highlights: h,
    summary: summary,
  }
end