Class: Charming::Internal::Terminal::MouseParser

Inherits:
Object
  • Object
show all
Defined in:
lib/charming/internal/terminal/mouse_parser.rb

Overview

MouseParser parses raw terminal escape sequences into MouseEvent objects. Supports both modern SGR sequences (the most common, used by current terminals) and the older 3-byte legacy sequences. The public API is class methods; no instance state is required.

Constant Summary collapse

SGR_PATTERN =

Matches an SGR-encoded mouse sequence: “e[<button;col;rowM”

/\e\[<(\d+);(\d+);(\d+)([HmMhCc]?)(M|m)/
LEGACY_PATTERN =

Matches the legacy 3-byte mouse sequence: “e[M” followed by 3 bytes.

/\e\[M(.{3})/
BUTTON_MAP =

Maps raw button codes to semantic symbols used by MouseEvent#button_name.

{
  0 => :left, 1 => :middle, 2 => :right, 3 => :release,
  64 => :scroll_up, 65 => :scroll_down,
  66 => :scroll_up, 67 => :scroll_down
}.freeze

Class Method Summary collapse

Class Method Details

.parse(raw) ⇒ Object

Parses raw into a MouseEvent, or returns nil when the string is not a mouse sequence or cannot be decoded.



36
37
38
39
40
41
42
# File 'lib/charming/internal/terminal/mouse_parser.rb', line 36

def self.parse(raw)
  return nil unless raw.is_a?(String)
  return parse_sgr(raw) if raw.match?(SGR_PATTERN)
  return parse_legacy(raw) if raw.start_with?("\e[M")

  nil
end

.parse_legacy(raw) ⇒ Object

Parses a legacy 3-byte mouse sequence. Each of the 3 bytes has 32 subtracted to recover the (button, col, row) values.



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/charming/internal/terminal/mouse_parser.rb', line 65

def self.parse_legacy(raw)
  match = raw.match(LEGACY_PATTERN)
  return nil unless match

  bytes = match[1].bytes
  return nil unless bytes.length == 3

  button_code = bytes[0] - 32
  col = bytes[1] - 32
  row = bytes[2] - 32

  Events::MouseEvent.new(button: button_code, x: col, y: row)
end

.parse_sgr(raw) ⇒ Object

Parses an SGR-format mouse sequence. Decodes button code, 1-based (col, row), the modifier “C” (ctrl) and “M” (shift) suffix, and the highlight alt (256-color) sequence as a heuristic for the alt modifier.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/charming/internal/terminal/mouse_parser.rb', line 47

def self.parse_sgr(raw)
  match = raw.match(SGR_PATTERN)
  return nil unless match

  button_code = match[1].to_i
  col = match[2].to_i - 1
  row = match[3].to_i - 1
  mode = match[4]

  ctrl = mode == "C"
  alt = raw.include?("\e[38;5;")
  shift = mode == "M"

  Events::MouseEvent.new(button: button_code, x: col, y: row, ctrl: ctrl, alt: alt, shift: shift)
end

.sequence?(raw) ⇒ Boolean

Returns true when raw looks like a recognizable mouse sequence (SGR or legacy). Lets the TTYBackend short-circuit and dispatch to MouseParser without allocation.

Returns:

  • (Boolean)


26
27
28
29
30
31
32
# File 'lib/charming/internal/terminal/mouse_parser.rb', line 26

def self.sequence?(raw)
  return false unless raw.is_a?(String)
  return true if raw.match?(SGR_PATTERN)
  return true if raw.start_with?("\e[M")

  false
end