Module: Clack::Core::OptionsHelper

Included in:
Prompts::Autocomplete, Prompts::AutocompleteMultiselect, Prompts::Multiselect, Prompts::Path, Prompts::Select
Defined in:
lib/clack/core/options_helper.rb

Overview

Shared functionality for option-based prompts (Select, Multiselect, Autocomplete, etc.). Handles option normalization, cursor navigation, and scrolling.

Including classes must define:

  • @max_items [Integer, nil] maximum visible items (nil = show all)
  • @option_index [Integer] current selection index
  • @scroll_offset [Integer] current scroll position

Including classes must implement:

  • navigable_items [Array] returns the current list to navigate

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.normalize_list(options) ⇒ Array<Option>

Normalize a list of raw options without the empty check. Used by prompts that build their own list (e.g. GroupMultiselect groups).

Parameters:

  • options (Array, Hash)

    Raw options in any accepted shape

Returns:

  • (Array<Option>)

    Normalized options



60
61
62
# File 'lib/clack/core/options_helper.rb', line 60

def self.normalize_list(options)
  option_entries(options).map { |opt| normalize_option(opt) }
end

.normalize_option(opt) ⇒ Option

Normalize a single option to an Option value object.

Parameters:

  • opt (Hash, String, Symbol)

    Raw option

Returns:

  • (Option)

    Normalized option



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/clack/core/options_helper.rb', line 93

def self.normalize_option(opt)
  case opt
  when Hash
    Option.new(
      value: opt[:value],
      label: opt[:label] || opt[:value].to_s,
      hint: opt[:hint],
      disabled: opt[:disabled] || false
    )
  else
    Option.new(value: opt, label: opt.to_s, hint: nil, disabled: false)
  end
end

.option_entries(options) ⇒ Array

Expand the Hash shorthand (value => label, or value => spec Hash) into the Array-of-entries shape that normalize_option understands. Anything that is not a Hash is returned unchanged.

Parameters:

  • options (Array, Hash)

    Raw options

Returns:

  • (Array)

    Option entries (hashes, strings, symbols, or other bare values)

Raises:

  • (ArgumentError)

    if a spec Hash sets :value



71
72
73
74
75
# File 'lib/clack/core/options_helper.rb', line 71

def self.option_entries(options)
  return options unless options.is_a?(Hash)

  options.map { |value, spec| hash_entry(value, spec) }
end

Instance Method Details

#find_initial_cursor(initial_value) ⇒ Integer

Find initial cursor position based on initial value or first enabled option.

Parameters:

  • initial_value (Object, nil)

    Initial value to select

Returns:

  • (Integer)

    Cursor position



179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/clack/core/options_helper.rb', line 179

def find_initial_cursor(initial_value)
  items = navigable_items
  return 0 if items.empty?

  if initial_value.nil?
    # Start at first enabled option
    return items[0].disabled ? first_enabled_index : 0
  end

  idx = items.find_index { |o| o.value == initial_value }
  (idx && !items[idx].disabled) ? idx : first_enabled_index
end

#find_next_enabled(from, delta) ⇒ Integer

Find the next enabled option in the given direction. Wraps around the list if necessary.

Parameters:

  • from (Integer)

    Starting index

  • delta (Integer)

    Direction (+1 for forward, -1 for backward)

Returns:

  • (Integer)

    Index of next enabled option, or from if all disabled



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/clack/core/options_helper.rb', line 113

def find_next_enabled(from, delta)
  items = navigable_items
  max = items.length
  idx = (from + delta) % max

  max.times do
    return idx unless items[idx].disabled

    idx = (idx + delta) % max
  end

  from
end

#first_enabled_indexInteger

Index of the first enabled option.

Returns:

  • (Integer)


129
130
131
# File 'lib/clack/core/options_helper.rb', line 129

def first_enabled_index
  find_next_enabled(-1, 1)
end

#move_cursor(delta) ⇒ Object

Move option_index in the given direction, skipping disabled options.

Parameters:

  • delta (Integer)

    Direction (+1 for down/right, -1 for up/left)



136
137
138
139
# File 'lib/clack/core/options_helper.rb', line 136

def move_cursor(delta)
  @option_index = find_next_enabled(@option_index, delta)
  update_scroll
end

#move_selection(delta) ⇒ Object

Move the selection index by delta, wrapping around. Unlike move_cursor, does not skip disabled items.

Parameters:

  • delta (Integer)

    direction (+1 for down, -1 for up)



145
146
147
148
149
150
151
# File 'lib/clack/core/options_helper.rb', line 145

def move_selection(delta)
  items = navigable_items
  return if items.empty?

  @option_index = (@option_index + delta) % items.length
  update_scroll
end

The list of items to navigate. Override in subclasses that use a filtered or dynamic list (e.g., Autocomplete uses @filtered).

Returns:

  • (Array)


195
196
197
# File 'lib/clack/core/options_helper.rb', line 195

def navigable_items
  @options
end

#normalize_options(options) ⇒ Array<Option>

Normalize options to a consistent list of Clack::Core::Option value objects.

Accepts an Array of strings, symbols, or hashes with value/label/hint/disabled keys, or a Hash of value => label (or value => hint:, disabled:).

Parameters:

  • options (Array, Hash)

    Raw options in any accepted shape

Returns:

  • (Array<Option>)

    Normalized options

Raises:

  • (ArgumentError)

    if options is nil or empty, or a Hash spec sets :value



49
50
51
52
53
# File 'lib/clack/core/options_helper.rb', line 49

def normalize_options(options)
  raise ArgumentError, "options cannot be empty" if options.nil? || options.empty?

  OptionsHelper.normalize_list(options)
end

#update_scrollObject

Update scroll offset to keep option_index visible within the window.



164
165
166
167
168
169
170
171
172
173
# File 'lib/clack/core/options_helper.rb', line 164

def update_scroll
  items = navigable_items
  return unless @max_items && items.length > @max_items

  if @option_index < @scroll_offset
    @scroll_offset = @option_index
  elsif @option_index >= @scroll_offset + @max_items
    @scroll_offset = @option_index - @max_items + 1
  end
end

#visible_optionsArray<Hash>

Get the currently visible options based on scroll offset and max_items.

Returns:

  • (Array<Hash>)

    Visible options



156
157
158
159
160
161
# File 'lib/clack/core/options_helper.rb', line 156

def visible_options
  items = navigable_items
  return items unless @max_items && items.length > @max_items

  items[@scroll_offset, @max_items]
end