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. Both encodings pack modifiers and motion into the button code: bit 2 shift, bit 3 alt/meta, bit 4 ctrl, bit 5 motion. SGR signals release with a final "m" instead of "M". 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;row" + "M" (press) or "m" (release).

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

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

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

Button-code bits for modifier keys and motion (shared by SGR and legacy).

4
ALT_BIT =
8
CTRL_BIT =
16
MOTION_BIT =
32
FLAG_BITS =
SHIFT_BIT | ALT_BIT | CTRL_BIT | MOTION_BIT

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.



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

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.



57
58
59
60
61
62
63
64
65
# File 'lib/charming/internal/terminal/mouse_parser.rb', line 57

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

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

  build_event(bytes[0] - 32, bytes[1] - 32, bytes[2] - 32, release: false)
end

.parse_sgr(raw) ⇒ Object

Parses an SGR-format mouse sequence: button code with flag bits, 1-based (col, row), and the press/release final byte.



48
49
50
51
52
53
# File 'lib/charming/internal/terminal/mouse_parser.rb', line 48

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

  build_event(match[1].to_i, match[2].to_i - 1, match[3].to_i - 1, release: match[4] == "m")
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)


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

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