Module: Clack::Core::Settings

Defined in:
lib/clack/core/settings.rb

Overview

Global configuration for key bindings, guide bar display, keyboard hint display, input classification, and the user-facing cancel/error strings.

Constant Summary collapse

ACTIONS =

Navigation and control actions

%i[up down left right space enter cancel].freeze
KEY_BACKSPACE =

Key code constants

"\b"
KEY_DELETE =

ASCII 8: Backspace

"\u007F"
KEY_CTRL_C =

ASCII 127: Delete (often sent by backspace key)

"\u0003"
KEY_CTRL_D =

ASCII 3: Ctrl+C (interrupt)

"\u0004"
KEY_ESCAPE =

ASCII 4: Ctrl+D (EOF, used for multiline submit)

"\e"
KEY_ENTER =

ASCII 27: Escape

"\r"
KEY_NEWLINE =

ASCII 13: Carriage return

"\n"
KEY_SPACE =

ASCII 10: Line feed

" "
KEY_HOME =

ASCII 32: Space

"\e[H"
KEY_END =

Canonical Home (CSI H)

"\e[F"
PRINTABLE_CHAR_MIN =

First printable ASCII character (space)

32
ALIASES =

Key to action mappings

{
  "k" => :up,
  "j" => :down,
  "h" => :left,
  "l" => :right,
  "\e[A" => :up,
  "\e[B" => :down,
  "\e[C" => :right,
  "\e[D" => :left,
  KEY_ENTER => :enter,
  KEY_NEWLINE => :enter,
  KEY_SPACE => :space,
  KEY_ESCAPE => :cancel,
  KEY_CTRL_C => :cancel
}.freeze
KEY_NORMALIZE =

Terminals encode the same key in several ways: SS3 (+ESC O x+) in application cursor mode, and Home/End as ESC [ H, ESC [ 1 ~ or ESC [ 7 ~ depending on the terminal. KeyReader folds every variant into the canonical code on the right, so prompts and aliases only ever deal with one code per key.

{
  "\eOA" => "\e[A",
  "\eOB" => "\e[B",
  "\eOC" => "\e[C",
  "\eOD" => "\e[D",
  "\eOH" => KEY_HOME,
  "\e[1~" => KEY_HOME,
  "\e[7~" => KEY_HOME,
  "\eOF" => KEY_END,
  "\e[4~" => KEY_END,
  "\e[8~" => KEY_END
}.freeze
DEFAULT_MESSAGES =

Default user-facing strings, overridable via update(messages:) for localization.

{cancel: "Cancelled", error: "Something went wrong"}.freeze
MESSAGE_KEYS =
DEFAULT_MESSAGES.keys.freeze

Class Method Summary collapse

Class Method Details

.action?(key) ⇒ Symbol?

Look up the action mapped to a key code.

Parameters:

Returns:

  • (Symbol, nil)

    the action (:up, :down, :enter, etc.) or nil



149
150
151
152
# File 'lib/clack/core/settings.rb', line 149

def action?(key)
  aliases = @config_mutex.synchronize { @config[:aliases] }
  aliases[key] if ACTIONS.include?(aliases[key])
end

.backspace?(key) ⇒ Boolean

Check if a key is a backspace/delete

Returns:

  • (Boolean)


167
168
169
# File 'lib/clack/core/settings.rb', line 167

def backspace?(key)
  [KEY_BACKSPACE, KEY_DELETE].include?(key)
end

.configHash

Get a copy of the current global config

Returns:

  • (Hash)

    Current configuration



78
79
80
# File 'lib/clack/core/settings.rb', line 78

def config
  @config_mutex.synchronize { @config.dup }
end

.message(key) ⇒ String

Look up one user-facing string.

Parameters:

  • key (Symbol)

    :cancel or :error

Returns:

  • (String)

Raises:

  • (KeyError)

    for an unknown key



113
# File 'lib/clack/core/settings.rb', line 113

def message(key) = messages.fetch(key)

.messagesHash{Symbol=>String}

Current cancel/error strings.

Returns:

  • (Hash{Symbol=>String})

    frozen



107
# File 'lib/clack/core/settings.rb', line 107

def messages = @config_mutex.synchronize { @config[:messages] }

.normalize_key(key) ⇒ String

Fold terminal-specific escape sequence variants into one canonical code.

Parameters:

  • key (String)

    key code as assembled by KeyReader

Returns:

  • (String)

    the canonical code (unchanged when no variant matches)



157
158
159
# File 'lib/clack/core/settings.rb', line 157

def normalize_key(key)
  KEY_NORMALIZE.fetch(key, key)
end

.printable?(key) ⇒ Boolean

Check if a key is a printable character (handles combining marks and multi-codepoint grapheme clusters)

Returns:

  • (Boolean)


162
163
164
# File 'lib/clack/core/settings.rb', line 162

def printable?(key)
  key && key.grapheme_clusters.length == 1 && key.ord >= PRINTABLE_CHAR_MIN
end

.reset!Object

Reset settings to defaults



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/clack/core/settings.rb', line 116

def reset!
  @config_mutex.synchronize do
    @config = {
      aliases: ALIASES.dup,
      with_guide: true,
      show_instructions: true,
      ci_mode: false,
      messages: DEFAULT_MESSAGES
    }
  end
end

.show_instructions?(override = nil) ⇒ Boolean

Resolve whether keyboard hint footers are shown.

Parameters:

  • override (Boolean, nil) (defaults to: nil)

    per-call value; nil means "use the global setting"

Returns:

  • (Boolean)


140
141
142
143
144
# File 'lib/clack/core/settings.rb', line 140

def show_instructions?(override = nil)
  return override unless override.nil?

  @config_mutex.synchronize { @config[:show_instructions] }
end

.update(aliases: nil, with_guide: nil, show_instructions: nil, ci_mode: nil, messages: nil) ⇒ Hash

Update global settings

Parameters:

  • aliases (Hash, nil) (defaults to: nil)

    Custom key to action mappings (merged with defaults)

  • with_guide (Boolean, nil) (defaults to: nil)

    Whether to show guide bars

  • show_instructions (Boolean, nil) (defaults to: nil)

    Whether prompts show their keyboard hint footer

  • ci_mode (Boolean, Symbol, nil) (defaults to: nil)

    CI mode: true (always), :auto (detect), false (never)

  • messages (Hash{Symbol=>String}, nil) (defaults to: nil)

    Cancel/error strings, merged with the current values: {cancel: "Cancelled", error: "Something went wrong"}

Returns:

  • (Hash)

    Updated configuration

Raises:

  • (ArgumentError)

    if messages is not a Hash, has an unknown key, or a non-String value



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/clack/core/settings.rb', line 91

def update(aliases: nil, with_guide: nil, show_instructions: nil, ci_mode: nil, messages: nil)
  validate_messages(messages) if messages
  @config_mutex.synchronize do
    @config[:aliases] = ALIASES.merge(aliases) if aliases
    @config[:with_guide] = with_guide unless with_guide.nil?
    @config[:show_instructions] = show_instructions unless show_instructions.nil?
    @config[:ci_mode] = ci_mode unless ci_mode.nil?
    # Values are frozen too (-value dedups without to_s), so a caller's
    # mutable String cannot change global state after the fact.
    @config[:messages] = @config[:messages].merge(messages.transform_values { |value| -value }).freeze if messages
    @config.dup
  end
end

.with_guide?(override = nil) ⇒ Boolean

Resolve whether guide bars are shown.

Parameters:

  • override (Boolean, nil) (defaults to: nil)

    per-call value; nil means "use the global setting"

Returns:

  • (Boolean)


131
132
133
134
135
# File 'lib/clack/core/settings.rb', line 131

def with_guide?(override = nil)
  return override unless override.nil?

  @config_mutex.synchronize { @config[:with_guide] }
end