Class: TansParser::State
- Inherits:
-
Object
- Object
- TansParser::State
- 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.
state.find_text("hello") # partial match (default) state.find_text("hello", match: :exact) # exact row match state.find_text("\\d+", match: :regex) # regex from string state.find_text(/\\d{3}/) # Regexp object (partial mode)Returns [{ row:, col:, text:, full_line: }, …].
textis the actual matched substring (for Regexp/:regex mode) or the pattern string (for :partial/:exact with String). 5- DEFAULT_CELL =
{ char: " ", fg: "default", bg: "default", bold: false, italic: false, underline: false, blink: false, }.freeze
Instance Attribute Summary collapse
-
#annotations ⇒ Object
readonly
Returns the value of attribute annotations.
-
#cols ⇒ Object
readonly
Returns the value of attribute cols.
-
#cursor ⇒ Object
readonly
Returns the value of attribute cursor.
-
#cursor_style ⇒ Object
readonly
Returns the value of attribute cursor_style.
-
#cursor_visible ⇒ Object
readonly
Returns the value of attribute cursor_visible.
-
#grid ⇒ Object
readonly
Returns the value of attribute grid.
-
#mouse_format ⇒ Object
readonly
Returns the value of attribute mouse_format.
-
#mouse_mode ⇒ Object
readonly
Returns the value of attribute mouse_mode.
-
#rows ⇒ Object
readonly
Returns the value of attribute rows.
Instance Method Summary collapse
-
#annotate_role(role, row:, col:, width: 1, height: 1, text: nil, **extra) ⇒ Object
Annotate a region of the terminal with a semantic role.
- #background_at(row, col) ⇒ Object
-
#diff(other_state, chars_only: false, ignore_rows: []) ⇒ Object
Compare this state with another State and return cell-level differences.
- #find_text(pattern, match: :partial) ⇒ Object
-
#foreground_at(row, col) ⇒ Object
Get the color at a specific cell.
-
#initialize(data) ⇒ State
constructor
A new instance of State.
-
#plain_text ⇒ Object
Get plain text of the entire terminal (no ANSI).
- #style_at(row, col) ⇒ Object
-
#text_at(row, col, length = @cols - col) ⇒ Object
Get text at a specific position.
- #to_ai_json ⇒ Object
Constructor Details
#initialize(data) ⇒ State
Returns a new instance of State.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# 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 @annotations = data[:annotations] || [] end |
Instance Attribute Details
#annotations ⇒ Object (readonly)
Returns the value of attribute annotations.
11 12 13 |
# File 'lib/tans_parser/state.rb', line 11 def annotations @annotations end |
#cols ⇒ Object (readonly)
Returns the value of attribute cols.
11 12 13 |
# File 'lib/tans_parser/state.rb', line 11 def cols @cols end |
#cursor ⇒ Object (readonly)
Returns the value of attribute cursor.
11 12 13 |
# File 'lib/tans_parser/state.rb', line 11 def cursor @cursor end |
#cursor_style ⇒ Object (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_visible ⇒ Object (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 |
#grid ⇒ Object (readonly)
Returns the value of attribute grid.
11 12 13 |
# File 'lib/tans_parser/state.rb', line 11 def grid @grid end |
#mouse_format ⇒ Object (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_mode ⇒ Object (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 |
#rows ⇒ Object (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
#annotate_role(role, row:, col:, width: 1, height: 1, text: nil, **extra) ⇒ Object
Annotate a region of the terminal with a semantic role. These annotations are picked up by Selector during element recognition. rubocop:disable Metrics/ParameterLists
35 36 37 38 |
# File 'lib/tans_parser/state.rb', line 35 def annotate_role(role, row:, col:, width: 1, height: 1, text: nil, **extra) @annotations << { role: role.to_sym, row: row, col: col, width: width, height: height, text: text, }.merge(extra) end |
#background_at(row, col) ⇒ Object
89 90 91 92 93 |
# File 'lib/tans_parser/state.rb', line 89 def background_at(row, col) return nil if row >= @rows || col >= @cols @grid[row][col][:bg] end |
#diff(other_state, chars_only: false, ignore_rows: []) ⇒ Object
Compare this state with another State and return cell-level differences. With chars_only: true, only differences in the :char key are reported. Use ignore_rows: to skip specific rows (e.g. cursor/prompt lines).
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/tans_parser/state.rb', line 132 def diff(other_state, chars_only: false, ignore_rows: []) other = other_state.is_a?(State) ? other_state : State.new(other_state) max_rows = [@rows, other.rows].max max_cols = [@cols, other.cols].max results = [] (0...max_rows).each do |r| next if ignore_rows.include?(r) (0...max_cols).each do |c| a = cell_at(r, c) b = other.send(:cell_at, r, c) next if chars_only ? a[:char] == b[:char] : a == b results << { row: r, col: c, before: a, after: b } end end results end |
#find_text(pattern, match: :partial) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/tans_parser/state.rb', line 66 def find_text(pattern, match: :partial) unless %i[partial exact regex].include?(match) raise ArgumentError, "unknown match mode: #{match.inspect}. Use :partial, :exact, or :regex" end results = [] case match when :exact find_text_exact(pattern, results) else compiled = compile_pattern(pattern, match) find_text_with_regex(compiled, results) end results end |
#foreground_at(row, col) ⇒ Object
Get the color at a specific cell
83 84 85 86 87 |
# File 'lib/tans_parser/state.rb', line 83 def foreground_at(row, col) return nil if row >= @rows || col >= @cols @grid[row][col][:fg] end |
#plain_text ⇒ Object
Get plain text of the entire terminal (no ANSI)
42 43 44 |
# File 'lib/tans_parser/state.rb', line 42 def plain_text @grid.map { |row| row.map { |c| c[:char] }.join.rstrip }.join("\n") end |
#style_at(row, col) ⇒ Object
95 96 97 98 99 100 |
# File 'lib/tans_parser/state.rb', line 95 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
47 48 49 50 51 |
# File 'lib/tans_parser/state.rb', line 47 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_json ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/tans_parser/state.rb', line 102 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 |