class TTY::Prompt::List
A class responsible for rendering select list menu Used by {Prompt} to display interactive menu.
@api private
Constants
- FILTER_KEYS_MATCHER
Allowed keys for filter, along with backspace and canc.
- INTEGER_MATCHER
Checks type of default parameter to be integer
Public Class Methods
Create instance of TTY::Prompt::List menu.
@param Hash options
the configuration options
@option options [Symbol] :default
the default active choice, defaults to 1
@option options [Symbol] :color
the color for the selected item, defualts to :green
@option options [Symbol] :marker
the marker for the selected item
@option options [String] :enum
the delimiter for the item index
@api public
# File lib/tty/prompt/list.rb, line 35 def initialize(prompt, **options) check_options_consistency(options) @prompt = prompt @prefix = options.fetch(:prefix) { @prompt.prefix } @enum = options.fetch(:enum) { nil } @default = Array(options[:default]) @choices = Choices.new @active_color = options.fetch(:active_color) { @prompt.active_color } @help_color = options.fetch(:help_color) { @prompt.help_color } @cycle = options.fetch(:cycle) { false } @filterable = options.fetch(:filter) { false } @symbols = @prompt.symbols.merge(options.fetch(:symbols, {})) @quiet = options.fetch(:quiet) { @prompt.quiet } @filter = [] @filter_cache = {} @help = options[:help] @show_help = options.fetch(:show_help) { :start } @first_render = true @done = false @per_page = options[:per_page] @paginator = Paginator.new @block_paginator = BlockPaginator.new @by_page = false @paging_changed = false end
Public Instance Methods
Information about arrow keys
@return [String]
@api private
# File lib/tty/prompt/list.rb, line 155 def arrows_help up_down = @symbols[:arrow_up] + "/" + @symbols[:arrow_down] left_right = @symbols[:arrow_left] + "/" + @symbols[:arrow_right] arrows = [up_down] arrows << "/" if paginated? arrows << left_right if paginated? arrows.join end
Call the list menu by passing question and choices
@param [String] question
@param @api public
# File lib/tty/prompt/list.rb, line 238 def call(question, possibilities, &block) choices(possibilities) @question = question block.call(self) if block setup_defaults @prompt.subscribe(self) do render end end
Add a single choice
@api public
# File lib/tty/prompt/list.rb, line 200 def choice(*value, &block) @filter_cache = {} if block @choices << (value << block) else @choices << value end end
Add multiple choices, or return them.
@param [Array] values
the values to add as choices; if not passed, the current choices are displayed.
@api public
# File lib/tty/prompt/list.rb, line 216 def choices(values = (not_set = true)) if not_set if !filterable? || @filter.empty? @choices else filter_value = @filter.join.downcase @filter_cache[filter_value] ||= @choices.enabled.select do |choice| choice.name.to_s.downcase.include?(filter_value) end end else @filter_cache = {} values.each { |val| @choices << val } end end
Set default option selected
@api public
# File lib/tty/prompt/list.rb, line 77 def default(*default_values) @default = default_values end
Default help text
Note that enumeration and filter are mutually exclusive
@a public
# File lib/tty/prompt/list.rb, line 170 def default_help str = [] str << "(Press " str << "#{arrows_help} arrow" str << " or 1-#{choices.size} number" if enumerate? str << " to move" str << (filterable? ? "," : " and") str << " Enter to select" str << " and letters to filter" if filterable? str << ")" str.join end
Set selecting active index using number pad
@api public
# File lib/tty/prompt/list.rb, line 186 def enum(value) @enum = value end
Check if list is enumerated
@return [Boolean]
# File lib/tty/prompt/list.rb, line 251 def enumerate? !@enum.nil? end
Provide help information
@param [String] value
the new help text
@return [String]
@api public
# File lib/tty/prompt/list.rb, line 135 def help(value = (not_set = true)) return @help if !@help.nil? && not_set @help = (@help.nil? && !not_set) ? value : default_help end
# File lib/tty/prompt/list.rb, line 366 def keybackspace(*) return unless filterable? @filter.pop @active = 1 end
# File lib/tty/prompt/list.rb, line 359 def keydelete(*) return unless filterable? @filter.clear @active = 1 end
# File lib/tty/prompt/list.rb, line 292 def keydown(*) searchable = ((@active + 1)..choices.length) next_active = search_choice_in(searchable) if next_active @active = next_active elsif @cycle searchable = (1..choices.length) next_active = search_choice_in(searchable) @active = next_active if next_active end @paging_changed = @by_page @by_page = false end
# File lib/tty/prompt/list.rb, line 265 def keyenter(*) @done = true unless choices.empty? end
# File lib/tty/prompt/list.rb, line 337 def keyleft(*) if (@active - page_size) > 0 searchable = ((@active - page_size)..choices.size) @active = search_choice_in(searchable) elsif @cycle searchable = choices.size.downto(1).to_a @active = search_choice_in(searchable) end @paging_changed = !@by_page @by_page = true end
# File lib/tty/prompt/list.rb, line 255 def keynum(event) return unless enumerate? value = event.value.to_i return unless (1..choices.count).cover?(value) return if choices[value - 1].disabled? @active = value end
# File lib/tty/prompt/list.rb, line 350 def keypress(event) return unless filterable? if event.value =~ FILTER_KEYS_MATCHER @filter << event.value @active = 1 end end
Moves all choices page by page keeping the current selected item at the same level on each page.
When the choice on a page is outside of next page range then adjust it to the last item, otherwise leave unchanged.
# File lib/tty/prompt/list.rb, line 314 def keyright(*) choices_size = choices.size if (@active + page_size) <= choices_size searchable = ((@active + page_size)..choices_size) @active = search_choice_in(searchable) elsif @active <= choices_size # last page shorter current = @active % page_size remaining = choices_size % page_size if current.zero? || (remaining > 0 && current > remaining) searchable = choices_size.downto(0).to_a @active = search_choice_in(searchable) elsif @cycle searchable = ((current.zero? ? page_size : current)..choices_size) @active = search_choice_in(searchable) end end @paging_changed = !@by_page @by_page = true end
# File lib/tty/prompt/list.rb, line 275 def keyup(*) searchable = (@active - 1).downto(1).to_a prev_active = search_choice_in(searchable) if prev_active @active = prev_active elsif @cycle searchable = choices.length.downto(1).to_a prev_active = search_choice_in(searchable) @active = prev_active if prev_active end @paging_changed = @by_page @by_page = false end
# File lib/tty/prompt/list.rb, line 114 def page_size (@per_page || Paginator::DEFAULT_PAGE_SIZE) end
Check if list is paginated
@return [Boolean]
@api private
# File lib/tty/prompt/list.rb, line 123 def paginated? choices.size > page_size end
Select paginator based on the current navigation key
@return [Paginator]
@api private
# File lib/tty/prompt/list.rb, line 86 def paginator @by_page ? @block_paginator : @paginator end
Set number of items per page
@api public
# File lib/tty/prompt/list.rb, line 110 def per_page(value) @per_page = value end
Set whether selected answers are echoed
@api public
# File lib/tty/prompt/list.rb, line 193 def quiet(value) @quiet = value end
# File lib/tty/prompt/list.rb, line 271 def search_choice_in(searchable) searchable.find { |i| !choices[i - 1].disabled? } end
Change when help is displayed
@api public
# File lib/tty/prompt/list.rb, line 144 def show_help(value = (not_set = true)) return @show_ehlp if not_set @show_help = value end
Change symbols used by this prompt
@param [Hash] new_symbols
the new symbols to use
@api public
# File lib/tty/prompt/list.rb, line 68 def symbols(new_symbols = (not_set = true)) return @symbols if not_set @symbols.merge!(new_symbols) end
Synchronize paginators start positions
@api private
# File lib/tty/prompt/list.rb, line 93 def sync_paginators if @by_page if @paginator.start_index @block_paginator.reset! @block_paginator.start_index = @paginator.start_index end else if @block_paginator.start_index @paginator.reset! @paginator.start_index = @block_paginator.start_index end end end
Private Instance Methods
Find value for the choice selected
@return [nil, Object]
@api private
# File lib/tty/prompt/list.rb, line 483 def answer choices[@active - 1].value end
# File lib/tty/prompt/list.rb, line 375 def check_options_consistency(options) if options.key?(:enum) && options.key?(:filter) raise ConfigurationError, "Enumeration can't be used with filter" end end
Header part showing the current filter
@return String
@api private
# File lib/tty/prompt/list.rb, line 524 def filter_help "(Filter: #{@filter.join.inspect})" end
Is filtering enabled?
@return [Boolean]
@api private
# File lib/tty/prompt/list.rb, line 515 def filterable? @filterable end
Check if help is always displayed
@api private
# File lib/tty/prompt/list.rb, line 538 def help_always? @show_help =~ /always/i end
Check if help is shown only on start
@api private
# File lib/tty/prompt/list.rb, line 531 def help_start? @show_help =~ /start/i end
Count how many screen lines the question spans
@return [Integer]
@api private
# File lib/tty/prompt/list.rb, line 472 def question_lines_count(question_lines) question_lines.reduce(0) do |acc, line| acc + @prompt.count_screen_lines(line) end end
Clear screen lines
@param [String]
@api private
# File lib/tty/prompt/list.rb, line 492 def refresh(lines) @prompt.clear_lines(lines) end
Render a selection list.
By default the result is printed out.
@return [Object] value
return the selected value
@api private
# File lib/tty/prompt/list.rb, line 448 def render @prompt.print(@prompt.hide) until @done question = render_question @prompt.print(question) @prompt.read_keypress # Split manually; if the second line is blank (when there are no # matching lines), it won't be included by using String#lines. question_lines = question.split($INPUT_RECORD_SEPARATOR, -1) @prompt.print(refresh(question_lines_count(question_lines))) end @prompt.print(render_question) unless @quiet answer ensure @prompt.print(@prompt.show) end
Render initial help and selected choice
@return [String]
@api private
# File lib/tty/prompt/list.rb, line 547 def render_header if @done selected_item = choices[@active - 1].name @prompt.decorate(selected_item.to_s, @active_color) elsif (@first_render && (help_start? || help_always?)) || (help_always? && !@filter.any?) @prompt.decorate(help, @help_color) elsif filterable? && @filter.any? @prompt.decorate(filter_help, @help_color) end end
Render question with instructions and menu
@return [String]
@api private
# File lib/tty/prompt/list.rb, line 501 def render_question header = ["#{@prefix}#{@question} #{render_header}\n"] @first_render = false unless @done header << render_menu end header.join end
Setup default option and active selection
@return [Integer]
@api private
# File lib/tty/prompt/list.rb, line 387 def setup_defaults validate_defaults if @default.empty? # no default, pick the first non-disabled choice @active = choices.index { |choice| !choice.disabled? } + 1 elsif @default.first.to_s =~ INTEGER_MATCHER @active = @default.first elsif default_choice = choices.find_by(:name, @default.first) @active = choices.index(default_choice) + 1 end end
Validate default choice name
@param [String] name
the name to verify
@return [String]
@api private
# File lib/tty/prompt/list.rb, line 431 def validate_default_name(name) default_choice = choices.find_by(:name, name.to_s) if default_choice.nil? "no choice found for the default name: #{name.inspect}" elsif default_choice.disabled? "default name #{name.inspect} matches disabled choice" end end
Validate default indexes to be within range
@raise [ConfigurationError]
raised when the default index is either non-integer, out of range or clashes with disabled choice item.
@api private
# File lib/tty/prompt/list.rb, line 407 def validate_defaults @default.each do |d| msg = if d.nil? || d.to_s.empty? "default index must be an integer in range (1 - #{choices.size})" elsif d.to_s !~ INTEGER_MATCHER validate_default_name(d) elsif d < 1 || d > choices.size "default index `#{d}` out of range (1 - #{choices.size})" elsif (dflt_choice = choices[d - 1]) && dflt_choice.disabled? "default index `#{d}` matches disabled choice" end raise(ConfigurationError, msg) if msg end end