Module: Howzit::Prompt

Defined in:
lib/howzit/prompt.rb

Overview

Command line prompt utils

Class Method Summary collapse

Class Method Details

.choose(matches, height: :auto) ⇒ Array

Choose from a list of items. If fzf is available, uses that, otherwise generates its own list of options and accepts a numeric response

Parameters:

  • matches (Array)

    The options list

  • height (Symbol) (defaults to: :auto)

    height of fzf menu (:auto adjusts height to number of options, anything else gets max height for terminal)

Returns:

  • (Array)

    the selected results



89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/howzit/prompt.rb', line 89

def choose(matches, height: :auto)
  return [] if !$stdout.isatty || matches.count.zero?

  if Util.command_exist?('fzf')
    height = height == :auto ? matches.count + 3 : TTY::Screen.rows

    settings = fzf_options(height)
    res = `echo #{Shellwords.escape(matches.join("\n"))} | fzf #{settings.join(' ')}`.strip
    return fzf_result(res)
  end

  tty_menu(matches)
end

.color_single_options(choices = %w[y n])) ⇒ String

Helper function to colorize the Y/N prompt

Parameters:

  • choices (Array) (defaults to: %w[y n]))

    The choices with default capitalized

Returns:

  • (String)

    colorized string



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/howzit/prompt.rb', line 46

def color_single_options(choices = %w[y n])
  out = []
  choices.each do |choice|
    case choice
    when /[A-Z]/
      out.push(Color.template("{bw}#{choice}{x}"))
    else
      out.push(Color.template("{dw}#{choice}{xg}"))
    end
  end
  Color.template("{xg}[#{out.join('/')}{xg}]{x}")
end

.fzf_options(height) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/howzit/prompt.rb', line 111

def fzf_options(height)
  [
    '-0',
    '-1',
    '-m',
    "--height=#{height}",
    '--header="Tab: add selection, ctrl-a/d: (de)select all, return: display/run"',
    '--bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all',
    '--prompt="Select a topic > "',
    %(--preview="howzit --no-pager --header-format block --no-color --default --multiple first {}")
  ]
end

.fzf_result(res) ⇒ Object



103
104
105
106
107
108
109
# File 'lib/howzit/prompt.rb', line 103

def fzf_result(res)
  if res.nil? || res.empty?
    Howzit.console.info 'Cancelled'
    Process.exit 0
  end
  res.split(/\n/)
end

.options_list(matches) ⇒ Object

Create a numbered list of options. Outputs directly to console, returns nothing

Parameters:

  • matches (Array)

    The list items



65
66
67
68
69
70
71
72
73
# File 'lib/howzit/prompt.rb', line 65

def options_list(matches)
  counter = 1
  puts
  matches.each do |match|
    printf("%<counter>2d ) %<option>s\n", counter: counter, option: match)
    counter += 1
  end
  puts
end

.read_editor(default = nil) ⇒ Object

Request editor



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/howzit/prompt.rb', line 165

def read_editor(default = nil)
  @stty_save = `stty -g`.chomp

  default ||= 'vim'
  prompt = "Define a default editor command (default #{default}): "
  res = Readline.readline(prompt, true).squeeze(' ').strip
  res = default if res.empty?

  Util.valid_command?(res) ? res : default
ensure
  system('stty', @stty_save)
end

.read_num(line) ⇒ Object

Convert a response to an Integer

Parameters:

  • line

    The response to convert



183
184
185
186
187
188
189
# File 'lib/howzit/prompt.rb', line 183

def read_num(line)
  if line =~ /^[a-z]/i
    system('stty', @stty_save) # Restore
    exit
  end
  line == '' ? 1 : line.to_i
end

.read_selection(matches) ⇒ Object

Read a single number response from the command line

Parameters:

  • matches

    The matches



148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/howzit/prompt.rb', line 148

def read_selection(matches)
  printf "Type 'q' to cancel, enter for first item"
  while (line = Readline.readline(': ', true))
    line = read_num(line)

    return [matches[line - 1]] if line.positive? && line <= matches.length

    puts 'Out of range'
    read_selection(matches)
  end
ensure
  system('stty', @stty_save)
end

.tty_menu(matches) ⇒ Object

Display a numeric menu on the TTY

Parameters:

  • matches

    The matches from which to select



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/howzit/prompt.rb', line 129

def tty_menu(matches)
  return matches if matches.count == 1

  @stty_save = `stty -g`.chomp

  trap('INT') do
    system('stty')
    exit
  end

  options_list(matches)
  read_selection(matches)
end

.yn(prompt, default: true) ⇒ Boolean

Display and read a Yes/No prompt

Parameters:

  • prompt (String)

    The prompt string

  • default (Boolean) (defaults to: true)

    default value if return is pressed or prompt is skipped

Returns:

  • (Boolean)

    result



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/howzit/prompt.rb', line 17

def yn(prompt, default: true)
  return default unless $stdout.isatty

  return true if Howzit.options[:yes]

  return false if Howzit.options[:no]

  return default if Howzit.options[:default]

  tty_state = `stty -g`
  system 'stty raw -echo cbreak isig'
  yn = color_single_options(default ? %w[Y n] : %w[y N])
  $stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
  res = $stdin.sysread 1
  res.chomp!
  puts
  system 'stty cooked'
  system "stty #{tty_state}"
  res.empty? ? default : res =~ /y/i
end