Class: RatatuiRuby::Event::Key

Inherits:
RatatuiRuby::Event show all
Includes:
Character, Dwim, Media, Modifier, Navigation, System
Defined in:
lib/ratatui_ruby/event/key.rb,
lib/ratatui_ruby/event/key/dwim.rb,
lib/ratatui_ruby/event/key/media.rb,
lib/ratatui_ruby/event/key/system.rb,
lib/ratatui_ruby/event/key/modifier.rb,
lib/ratatui_ruby/event/key/character.rb,
lib/ratatui_ruby/event/key/navigation.rb

Overview

Captures a keyboard interaction.

The keyboard is the primary interface for your terminal application. Raw key codes are often cryptic, and handling modifiers manually is error-prone.

This event creates clarity. It encapsulates the interaction, providing a normalized code and a list of active modifiers.

Compare it directly to strings or symbols for rapid development, or use pattern matching for complex control schemes.

Examples

Using predicates: – SPDX-SnippetBegin SPDX-FileCopyrightText: 2025 Kerrick Long SPDX-License-Identifier: MIT-0 ++

if event.key? && event.ctrl? && event.code == "c"
  exit
end

– SPDX-SnippetEnd ++ Using symbol comparison: – SPDX-SnippetBegin SPDX-FileCopyrightText: 2025 Kerrick Long SPDX-License-Identifier: MIT-0 ++

if event == :ctrl_c
  exit
end

– SPDX-SnippetEnd ++ Using pattern matching: – SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

case event
in type: :key, code: "c", modifiers: ["ctrl"]
  exit
end

– SPDX-SnippetEnd ++

Terminal Compatibility

Some key combinations never reach your application. Terminal emulators intercept them for built-in features like tab switching. Common culprits:

  • Ctrl+PageUp/PageDown (tab switching in Terminal.app, iTerm2)

  • Ctrl+Tab (tab switching)

  • Cmd+key combinations (macOS system shortcuts)

If modifiers appear missing, test with a different terminal. Kitty, WezTerm, and Alacritty pass more keys through. See doc/terminal_limitations.md for details.

Enhanced Keys (Kitty Protocol)

Terminals supporting the Kitty keyboard protocol report additional keys:

  • Media keys: :play, :play_pause, :track_next, :mute_volume

  • Individual modifiers: :left_shift, :right_control, :left_super

These keys will not work in Terminal.app, iTerm2, or GNOME Terminal.

Defined Under Namespace

Modules: Character, Dwim, Media, Modifier, Navigation, System

Constant Summary

Constants included from Dwim

Dwim::ARROW_KEYS, Dwim::NAVIGATION_KEYS, Dwim::PUNCTUATION_NAMES, Dwim::VIM_MOVEMENT_KEYS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from System

#function?, #system?

Methods included from Navigation

#standard?

Methods included from Modifier

#alt?, #ctrl?, #hyper?, #meta?, #modifier?, #shift?, #super?

Methods included from Media

#media?

Methods included from Dwim

#alphanumeric?, #arrow?, #cancel?, #cr?, #digit?, #eof?, #interrupt?, #letter?, #navigation?, #punctuation?, #quit?, #quote?, #sigint?, #space?, #suspend?, #vim?, #vim_bottom?, #vim_down?, #vim_left?, #vim_right?, #vim_top?, #vim_up?, #vim_word_backward?, #vim_word_forward?, #whitespace?

Methods included from Character

#char, #text?

Methods inherited from RatatuiRuby::Event

#focus_gained?, #focus_lost?, #mouse?, #none?, #paste?, #resize?, #sync?

Constructor Details

#initialize(code:, modifiers: [], kind: :standard) ⇒ Key

Creates a new Key event.

code

The key code (String).

modifiers

List of modifiers (Array<String>).

kind

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

The key category (Symbol). One of: <tt>:standard</tt>, <tt>:function</tt>,
<tt>:media</tt>, <tt>:modifier</tt>, <tt>:system</tt>. Defaults to <tt>:standard</tt>.

– SPDX-SnippetEnd ++



151
152
153
154
155
# File 'lib/ratatui_ruby/event/key.rb', line 151

def initialize(code:, modifiers: [], kind: :standard)
  @code = code.freeze
  @modifiers = modifiers.map(&:freeze).sort.freeze
  @kind = kind
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, **kwargs, &block) ⇒ Object

Supports dynamic key predicate methods via method_missing.

Allows convenient checking for specific keys or key combinations:

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2025 Kerrick Long SPDX-License-Identifier: MIT-0 ++

event.ctrl_c?     # => true if Ctrl+C
event.enter?      # => true if Enter
event.shift_up?   # => true if Shift+Up
event.q?          # => true if "q"

– SPDX-SnippetEnd ++ The method name is converted to a symbol and compared against the event. This works for any key code or modifier+key combination.

Smart Predicates (DWIM)

For convenience, generic predicates match both system and media variants:

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

event.pause?      # => true for BOTH system "pause" AND "media_pause"
event.play?       # => true for "media_play"
event.stop?       # => true for "media_stop"

– SPDX-SnippetEnd ++ This “Do What I Mean” behavior reduces boilerplate when you just want to respond to a conceptual action (e.g., “pause the playback”) regardless of whether the user pressed a keyboard key or a media button.

For strict matching, use the full predicate or compare the code directly:

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

event.media_pause?  # => true ONLY for media pause
event.code == "pause"  # => true ONLY for system pause

– SPDX-SnippetEnd ++

Arrow Key Aliases

Arrow keys respond to arrow_up? and up_arrow? variants. This disambiguates from Mouse events, which also respond to up? and down?:

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

event.arrow_up?     # => true for up arrow key
event.up_arrow?     # => true for up arrow key
event.arrow_down?   # => true for down arrow key

– SPDX-SnippetEnd ++

Key Prefix and Suffix

Predicates accept key_ prefix or _key suffix for explicit key event matching in mixed event contexts:

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

event.key_up?       # => true for up arrow key
event.key_q?        # => true for "q" key
event.q_key?        # => true for "q" key
event.enter_key?    # => true for enter key

– SPDX-SnippetEnd ++

Capital Letters and Shift

Capital letter predicates match shifted keys naturally. The terminal reports the produced character with shift in the modifiers:

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

event.G?            # => true for code="G" modifiers=["shift"]
event.shift_g?      # => true for code="G" modifiers=["shift"]
event.alt_B?        # => true for code="B" modifiers=["alt", "shift"]

– SPDX-SnippetEnd ++



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/ratatui_ruby/event/key.rb', line 389

def method_missing(name, *args, **kwargs, &block)
  if name.to_s.end_with?("?")
    name_str = name.to_s
    key_name = name_str.chop # Returns String, never nil for non-empty string
    key_sym = key_name.to_sym

    # Fast path: Exact match (e.g., media_pause? for media_pause)
    return true if self == key_sym

    # Delegate category-specific DWIM logic to mixins
    return true if match_media_dwim?(key_name)
    return true if match_modifier_dwim?(key_name, key_sym)
    return true if match_navigation_dwim?(key_name, key_sym)
    return true if match_system_dwim?(key_name, key_sym)

    # DWIM: key_ prefix and _key suffix (disambiguate from mouse events)
    # key_up? → up?, q_key? → q?, etc.
    key_name = key_name.delete_prefix("key_").delete_suffix("_key")

    # Fast path after prefix/suffix stripping
    return true if self == key_name.to_sym

    # DWIM: Single character codes match even with shift modifier present
    # G? matches code="G" modifiers=["shift"], B? matches code="B" modifiers=["alt","shift"]
    # @? matches code="@" modifiers=["shift"]
    # The terminal reports the produced character, shift is implicit for these
    if key_name.length == 1 && @code == key_name && @modifiers.include?("shift")
      return true
    end

    # DWIM: Uppercase in predicate implies shift, so alt_B? matches alt_shift_B
    # Parse predicate to extract modifiers and final letter
    if key_name.match?(/\A([a-z_]+_)?([A-Z])\z/)
      pred_letter = key_name[-1]
      pred_mods = key_name.chop.delete_suffix("_").split("_").reject(&:empty?)
      expected_mods = (pred_mods + ["shift"]).sort
      return true if @code == pred_letter && @modifiers == expected_mods
    end

    # DWIM: Case-insensitive letter matching with modifiers
    # shift_g? matches code="G" modifiers=["shift"]
    if @code.length == 1 && @code.match?(/[A-Za-z]/) && (to_sym.to_s.downcase == key_name.downcase)
      return true
    end

    # DWIM: Universal underscore-insensitivity
    # Normalize both predicate and code by stripping underscores
    normalized_predicate = key_name.delete("_")
    normalized_code = @code.delete("_")
    return true if normalized_predicate == normalized_code && @modifiers.empty?

    # DWIM: Underscore variants delegate to existing methods
    # space_bar? → spacebar? → space?, sig_int? → sigint?
    normalized_method = :"#{normalized_predicate}?"
    if normalized_method != name && respond_to?(normalized_method)
      return public_send(normalized_method)
    end

    false
  else
    super
  end
end

Instance Attribute Details

#codeObject (readonly)

The key code (e.g., "a", "enter", "up").

puts event.code # => "enter"


101
102
103
# File 'lib/ratatui_ruby/event/key.rb', line 101

def code
  @code
end

#kindObject (readonly)

The category of the key.

One of: :standard, :function, :media, :modifier, :system.

This allows grouping keys by their logical type without parsing the code string.

event.kind # => :media


115
116
117
# File 'lib/ratatui_ruby/event/key.rb', line 115

def kind
  @kind
end

#modifiersObject (readonly)

List of active modifiers ("ctrl", "alt", "shift").

puts event.modifiers # => ["ctrl", "shift"]


106
107
108
# File 'lib/ratatui_ruby/event/key.rb', line 106

def modifiers
  @modifiers
end

Instance Method Details

#==(other) ⇒ Object

Compares the event with another object.

  • If other is a Symbol, compares against #to_sym.

  • If other is a String, compares against #to_s.

  • If other is a Key, compares as a value object.

  • Otherwise, compares using standard equality.



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ratatui_ruby/event/key.rb', line 163

def ==(other)
  case other
  when Symbol
    to_sym == other
  when String
    to_s == other
  when Key
    code == other.code && modifiers == other.modifiers
  else
    super
  end
end

#deconstruct_keys(keys) ⇒ Object

Deconstructs the event for pattern matching.

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

case event
in type: :key, code: "c", modifiers: ["ctrl"]
  puts "Ctrl+C pressed"
in type: :key, kind: :media
  puts "Media key pressed"
end

– SPDX-SnippetEnd ++



474
475
476
# File 'lib/ratatui_ruby/event/key.rb', line 474

def deconstruct_keys(keys)
  { type: :key, code: @code, modifiers: @modifiers, kind: @kind }
end

#inspectObject

Returns inspection string.



279
280
281
# File 'lib/ratatui_ruby/event/key.rb', line 279

def inspect
  "#<#{self.class} code=#{@code.inspect} modifiers=#{@modifiers.inspect} kind=#{@kind.inspect}>"
end

#key?Boolean

Returns true for Key events.

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2025 Kerrick Long SPDX-License-Identifier: MIT-0 ++

event.key?    # => true
event.mouse?  # => false
event.resize? # => false

– SPDX-SnippetEnd ++

Returns:

  • (Boolean)


130
131
132
# File 'lib/ratatui_ruby/event/key.rb', line 130

def key?
  true
end

#respond_to_missing?(name, *args) ⇒ Boolean

Declares that this class responds to dynamic predicate methods.

Returns:

  • (Boolean)


454
455
456
# File 'lib/ratatui_ruby/event/key.rb', line 454

def respond_to_missing?(name, *args)
  name.to_s.end_with?("?") || super
end

#to_sObject

Converts the event to its String representation.

Printable Characters

Returns the character itself (e.g., "a", "1", " ").

Special Keys

Returns an empty string (e.g., "enter", "up", "f1" all return "").

Modifiers

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2025 Kerrick Long SPDX-License-Identifier: MIT-0 ++

Returns the character if printable, ignoring modifiers unless they alter the character code itself.
Note that <tt>ctrl+c</tt> typically returns <tt>"c"</tt> as the code, so +to_s+ will return <tt>"c"</tt>.

– SPDX-SnippetEnd ++



270
271
272
273
274
275
276
# File 'lib/ratatui_ruby/event/key.rb', line 270

def to_s
  if text?
    @code
  else
    ""
  end
end

#to_symObject

Converts the event to a Symbol representation.

The format is [modifiers_]code. Modifiers are sorted alphabetically (alt, ctrl, shift) and joined by underscores.

Supported Keys

Standard

:enter, :backspace, :tab, :back_tab, :esc, :null

Navigation

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

<tt>:up</tt>, <tt>:down</tt>, <tt>:left</tt>, <tt>:right</tt>, <tt>:home</tt>, <tt>:end</tt>,
<tt>:page_up</tt>, <tt>:page_down</tt>, <tt>:insert</tt>, <tt>:delete</tt>

– SPDX-SnippetEnd ++

Function Keys

:f1 through :f12 (and beyond, e.g. :f24)

Lock Keys

:caps_lock, :scroll_lock, :num_lock

System Keys

:print_screen, :pause, :menu, :keypad_begin

Media Keys

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

<tt>:play</tt>, <tt>:media_pause</tt>, <tt>:play_pause</tt>, <tt>:reverse</tt>, <tt>:stop</tt>,
<tt>:fast_forward</tt>, <tt>:rewind</tt>, <tt>:track_next</tt>, <tt>:track_previous</tt>,
<tt>:record</tt>, <tt>:lower_volume</tt>, <tt>:raise_volume</tt>, <tt>:mute_volume</tt>

– SPDX-SnippetEnd ++

Modifier Keys

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

<tt>:left_shift</tt>, <tt>:left_control</tt>, <tt>:left_alt</tt>, <tt>:left_super</tt>,
<tt>:left_hyper</tt>, <tt>:left_meta</tt>, <tt>:right_shift</tt>, <tt>:right_control</tt>,
<tt>:right_alt</tt>, <tt>:right_super</tt>, <tt>:right_hyper</tt>, <tt>:right_meta</tt>,
<tt>:iso_level3_shift</tt>, <tt>:iso_level5_shift</tt>

– SPDX-SnippetEnd ++

Characters

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2025 Kerrick Long SPDX-License-Identifier: MIT-0 ++

<tt>:a</tt>, <tt>:b</tt>, <tt>:1</tt>, <tt>:space</tt>, etc.

– SPDX-SnippetEnd ++

Modifier Examples

  • :ctrl_c

  • :alt_enter

  • :shift_left

  • :ctrl_alt_delete



244
245
246
247
248
249
250
251
# File 'lib/ratatui_ruby/event/key.rb', line 244

def to_sym
  mods = @modifiers.join("_")
  if mods.empty?
    @code.to_sym
  else
    :"#{mods}_#{@code}"
  end
end