natty-ui 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db28cf57db8670d12ed258d70765447c08bd51462acc7907ea9072b64117f965
4
- data.tar.gz: 36e9c43bf76fcb1e68b450a1289bcffa8b4cfb33fa2d575ed9b1b92dc6ecce69
3
+ metadata.gz: 3573e7d7a6d163f278494a095198e3fd913ff6710c4984ff12279f80893c1b54
4
+ data.tar.gz: 9d424a564160fa470b71d857b31fa725320b21047abd8423e613335b0e0449c6
5
5
  SHA512:
6
- metadata.gz: e19e8e97300a844d294f61e9d4e6c43f5db5fe5a7122ff7c07c7bdd460e229ffffb0b6c943fe1e9f015bf91008824312746290098e10c7851ece75b7bfcb21bf
7
- data.tar.gz: c6caff65104a42913c3f38ad53b0d628b11aff7517f6450d562c7f41e3b99234caaa48d8e266dd7abb6ca3a83546cd27a019612d0ee6f9e40e249717d26ecfd1
6
+ metadata.gz: f2b6c3c4bc0c8636bbed5dcfcfb5cc9f77487ef84be7650abba172b707cf2e9eb9c59b9a4f0ba3c2209ce4902c704f8920cbd99d63d7da3cd347dbf466afe689
7
+ data.tar.gz: 2c514b126360dd2c2b6173e9944db8e3cc1bd5a5505d5176ebaa382cf9a810dccc59137c55d1b18d7f1229ac8cbcee5fa9553ad5763ecea30fa84cabc0430387
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  This is the beautiful, nice, nifty, fancy, neat, pretty, cool, rich, lovely, natty user interface you like to have for your command line applications. It contains elegant, simple and beautiful tools that enhance your command line interfaces functionally and aesthetically.
4
4
 
5
5
  - Gem: [rubygems.org](https://rubygems.org/gems/natty-ui)
6
- - Source: [github.com](https://github.com/mblumtritt/natty-ui)
6
+ - Source: [codeberg.org](https://codeberg.org/mblumtritt/natty-ui)
7
7
  - Help: [rubydoc.info](https://rubydoc.info/gems/natty-ui/NattyUI)
8
8
 
9
9
  ## Features
@@ -33,13 +33,13 @@ This is the beautiful, nice, nifty, fancy, neat, pretty, cool, rich, lovely, nat
33
33
 
34
34
  You can execute all examples by
35
35
 
36
- ```sh
36
+ ```shell
37
37
  ruby ./examples/examples.rb
38
38
  ```
39
39
 
40
40
  or see the non-ANSI version
41
41
 
42
- ```sh
42
+ ```shell
43
43
  NO_COLOR=1 ruby ./examples/examples.rb
44
44
  ```
45
45
 
data/examples/examples.rb CHANGED
@@ -16,11 +16,10 @@ EXAMPLES = {
16
16
  'vbars' => 'Print Vertical Bars',
17
17
  'hbars' => 'Print Horizontal Bars',
18
18
  'sections' => 'Sections',
19
- 'tasks' => 'Tasks'
19
+ 'tasks' => 'Tasks',
20
+ 'options' => 'Options and Selections'
20
21
  }.freeze
21
22
 
22
- DEFAULT_MODE = NattyUI.input_mode == :default
23
-
24
23
  ui.space
25
24
 
26
25
  while true
@@ -34,12 +33,12 @@ while true
34
33
  ui.div('[faint](Abort with [\\ESC])', padding: [1, 0, 1])
35
34
  end
36
35
 
37
- ui.space unless DEFAULT_MODE
36
+ ui.space unless Terminal.ansi?
38
37
  break unless selected
39
38
 
40
39
  ui.temporary do
41
40
  load("#{__dir__}/#{selected}.rb")
42
- if DEFAULT_MODE
41
+ if Terminal.ansi?
43
42
  ui.await { ui.puts '[faint][\\Press ENTER to continue...]' }
44
43
  else
45
44
  ui.space
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/natty-ui'
4
+
5
+ ui.message '[b]​ᓚᕠᗢ NattyUI[/b] [i green]Options and Selections[/]' do
6
+ ui.space
7
+ ui.puts <<~INFO, eol: false
8
+ [i]Options[/i] and [i]selections[/i] allow the user to select from several
9
+ options interactively.
10
+ If ANSI is available the user can use [b][Up][/b] and [b][Down][/b] keys to
11
+ navigate, switch the state of selected item with [b][Space][/b] and complete
12
+ the selection with [b][Enter][/b].
13
+ INFO
14
+
15
+ ui.space
16
+ options =
17
+ ui.select %w[Kitty iTerm2 Ghostty Tabby Rio] do
18
+ ui.puts '[i]Which terminal applications did you already tested?[/i]'
19
+ end
20
+ case options.size
21
+ when 0
22
+ ui.puts 'You selected nothing – test more in future!'
23
+ when 1
24
+ ui.puts "#{options.first} is indeed a nice terminal."
25
+ else
26
+ ui.puts "#{options.join(', ')} are worth to test!"
27
+ end
28
+ end
@@ -29,7 +29,6 @@ module NattyUI
29
29
  def _init = nil
30
30
  def _assign(_opt) = self
31
31
  def _store(opt) = opt
32
-
33
32
  def as_uint(value) = [0, value.to_i].max
34
33
  def as_nint(value) = ([0, value.to_i].max if value)
35
34
 
@@ -555,9 +554,7 @@ module NattyUI
555
554
 
556
555
  module TextWithAttributes
557
556
  include WithAttributes
558
-
559
557
  attr_reader :text
560
-
561
558
  def empty? = @text.empty?
562
559
  alias _to_s to_s
563
560
  private :_to_s
@@ -7,24 +7,31 @@ module NattyUI
7
7
  def select
8
8
  yield(self) if block_given?
9
9
  pin_line = NattyUI.lines_written
10
- draw(current = @ret.index(@selected) || 0)
10
+ draw
11
+ last = @current
11
12
  while (event = Terminal.read_key_event)
12
13
  case event.name
13
14
  when 'Esc', 'Ctrl+c'
14
15
  break nil if @abortable
15
- when 'Enter', ' '
16
- break @ret[current]
16
+ when 'Enter', 'Space'
17
+ break @ret[@current]
17
18
  when 'Home'
18
- current = 0
19
+ @current = 0
19
20
  when 'End'
20
- current = @texts.size - 1
21
- when 'Up', 'Back', 'Shift+Tab', 'i'
22
- current = @texts.size - 1 if (current -= 1) < 0
23
- when 'Down', 'Tab', 'k'
24
- current = 0 if (current += 1) == @texts.size
21
+ @current = @texts.size - 1
22
+ when 'Up', 'Back', 'Shift+Tab', 'i', 'w'
23
+ @current = @texts.size - 1 if (@current -= 1) < 0
24
+ when 'Down', 'Tab', 'k', 's'
25
+ @current = 0 if (@current += 1) == @texts.size
26
+ else
27
+ next unless event.simple?
28
+ c = event.key.ord
29
+ @current = (c - 48).clamp(0, @texts.size - 1) if c.between?(48, 57)
25
30
  end
31
+ next if last == @current
26
32
  pin_line = NattyUI.back_to_line(pin_line, erase: false)
27
- draw(current)
33
+ draw
34
+ last = @current
28
35
  end
29
36
  ensure
30
37
  NattyUI.back_to_line(@start_line)
@@ -38,17 +45,17 @@ module NattyUI
38
45
  @texts = args + kwargs.values
39
46
  @ret = Array.new(args.size, &:itself) + kwargs.keys
40
47
  @abortable = abortable
41
- @selected = selected
48
+ @current = @ret.index(selected) || 0
42
49
  theme = Theme.current
43
50
  @mark = [theme.mark(:choice), theme.choice_style]
44
51
  @mark_current = [theme.mark(:current_choice), theme.choice_current_style]
45
52
  end
46
53
 
47
- def draw(current)
54
+ def draw
48
55
  @texts.each_with_index do |str, idx|
49
- mark, decor = idx == current ? @mark_current : @mark
56
+ mark, style = idx == @current ? @mark_current : @mark
50
57
  @parent.puts(
51
- "#{decor}#{str}",
58
+ "#{style}#{str}",
52
59
  first_line_prefix: mark,
53
60
  first_line_prefix_width: mark.width
54
61
  )
@@ -11,7 +11,8 @@ module NattyUI
11
11
  return if @abortable && %w[Esc Ctrl+c].include?(event.name)
12
12
  next unless event.simple?
13
13
  code = event.raw.upcase
14
- if @ret.size <= 9 && ('1'..'9').include?(code)
14
+ if @ret.size <= 9
15
+ next unless ('1'..'9').include?(code)
15
16
  code = @ret[code.ord - 49] and break code
16
17
  elsif ('A'..'Z').include?(code)
17
18
  code = @ret[code.ord - 65] and break code
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element'
4
+
5
+ module NattyUI
6
+ class DumbOptions < Element
7
+ def select
8
+ yield(self) if block_given?
9
+ draw
10
+ while (event = Terminal.read_key_event)
11
+ return if @abortable && %w[Esc Ctrl+c].include?(event.name)
12
+ return @opts.transform_values(&:last) if event.name == 'Enter'
13
+ next unless event.simple?
14
+ code = event.raw.upcase
15
+ if @opts.size <= 9
16
+ next unless ('1'..'9').include?(code)
17
+ offset = 49
18
+ elsif ('A'..'Z').include?(code)
19
+ offset = 65
20
+ else
21
+ next
22
+ end
23
+ key = @opts.keys[code.ord - offset] or next
24
+ @opts[key][-1] = !@opts[key][-1]
25
+ @parent.space
26
+ draw
27
+ end
28
+ ensure
29
+ @parent.space
30
+ end
31
+
32
+ private
33
+
34
+ def initialize(parent, opts, abortable, selected)
35
+ super(parent)
36
+ @opts =
37
+ opts.to_h do |k, v|
38
+ v.is_a?(Enumerable) ? [k, [v[0], !!v[-1]]] : [k, [k, !!v]]
39
+ end
40
+ @abortable = abortable
41
+ @current = @opts.key?(selected) ? selected : @opts.first.first
42
+ theme = Theme.current
43
+ @marks = {
44
+ true => theme.mark(:checkmark),
45
+ false => theme.mark(:choice)
46
+ }.compare_by_identity
47
+ end
48
+
49
+ def draw
50
+ glyph = @opts.size <= 9 ? 1 : 'A'
51
+ @opts.each_pair do |_, (str, selected)|
52
+ mark = @marks[selected]
53
+ @parent.puts(
54
+ str,
55
+ first_line_prefix: "[\\#{glyph}] #{mark}",
56
+ first_line_prefix_width: mark.width + 2
57
+ )
58
+ glyph = glyph.succ
59
+ end
60
+ end
61
+ end
62
+ private_constant :DumbOptions
63
+ end
@@ -32,12 +32,12 @@ module NattyUI
32
32
  self
33
33
  end
34
34
 
35
- alias _to_s to_s
36
- private :_to_s
37
-
38
35
  # @!visibility private
39
36
  alias to_s inspect
40
37
 
38
+ alias _to_s to_s
39
+ private :_to_s
40
+
41
41
  private
42
42
 
43
43
  def initialize(parent)
@@ -48,7 +48,6 @@ module NattyUI
48
48
 
49
49
  module WithStatus
50
50
  attr_reader :status
51
-
52
51
  def active? = @status.nil?
53
52
  def closed? = !active?
54
53
  def ok? = @state == :ok
@@ -37,44 +37,67 @@ module NattyUI
37
37
  # @return [Features]
38
38
  # itself
39
39
  def puts(*text, **options)
40
- bbcode = true if (bbcode = options[:bbcode]).nil?
41
- max_width = options[:max_width] || Terminal.columns
42
- max_width = Terminal.columns + max_width if max_width < 0
43
-
44
- prefix_width =
45
- if (prefix = options[:prefix])
46
- prefix = Ansi.bbcode(prefix) if bbcode
47
- options[:prefix_width] || Text.width(prefix, bbcode: false)
48
- else
49
- 0
50
- end
40
+ if (ansi = Terminal.ansi?)
41
+ @__eol ||= "\e[m\n"
42
+ else
43
+ @__eol ||= "\n"
44
+ end
51
45
 
52
- if (first_line = options[:first_line_prefix])
53
- first_line = Ansi.bbcode(first_line) if bbcode
54
- first_line_width =
55
- options[:first_line_prefix_width] ||
56
- Text.width(first_line, bbcode: false)
46
+ if options.empty?
47
+ bbcode = true
48
+ max_width = Terminal.columns
49
+ else
50
+ bbcode = true if (bbcode = options[:bbcode]).nil?
51
+ ignore_newline = options[:eol] == false || options[:ignore_newline]
52
+
53
+ if (max_width = options[:max_width]).nil?
54
+ return self if (max_width = Terminal.columns).zero?
55
+ elsif max_width < 1
56
+ if max_width > 0
57
+ max_width *= Terminal.columns
58
+ elsif max_width < 0
59
+ max_width += Terminal.columns
60
+ else
61
+ return self
62
+ end
63
+ end
57
64
 
58
- if prefix_width < first_line_width
59
- prefix_next = "#{prefix}#{' ' * (first_line_width - prefix_width)}"
60
- prefix = first_line
61
- prefix_width = first_line_width
62
- else
63
- prefix_next = prefix
64
- prefix =
65
- if first_line_width < prefix_width
66
- first_line + (' ' * (prefix_width - first_line_width))
67
- else
68
- first_line
69
- end
65
+ prefix_width =
66
+ if (prefix = options[:prefix])
67
+ prefix = Ansi.bbcode(prefix) if bbcode
68
+ options[:prefix_width] || Text.width(prefix, bbcode: false)
69
+ else
70
+ 0
71
+ end
72
+
73
+ if (first_line = options[:first_line_prefix])
74
+ first_line = Ansi.bbcode(first_line) if bbcode
75
+ first_line_width =
76
+ options[:first_line_prefix_width] ||
77
+ Text.width(first_line, bbcode: false)
78
+
79
+ if prefix_width < first_line_width
80
+ prefix_next = "#{prefix}#{' ' * (first_line_width - prefix_width)}"
81
+ prefix = first_line
82
+ prefix_width = first_line_width
83
+ else
84
+ prefix_next = prefix
85
+ prefix =
86
+ if first_line_width < prefix_width
87
+ first_line + (' ' * (prefix_width - first_line_width))
88
+ else
89
+ first_line
90
+ end
91
+ end
70
92
  end
71
- end
72
93
 
73
- max_width -= prefix_width
94
+ max_width -= prefix_width
74
95
 
75
- if (suffix = options[:suffix])
76
- suffix = Ansi.bbcode(suffix) if bbcode
77
- max_width -= options[:suffix_width] || Text.width(suffix, bbcode: false)
96
+ if (suffix = options[:suffix])
97
+ suffix = Ansi.bbcode(suffix) if bbcode
98
+ max_width -=
99
+ options[:suffix_width] || Text.width(suffix, bbcode: false)
100
+ end
78
101
  end
79
102
 
80
103
  return self if max_width <= 0
@@ -84,13 +107,13 @@ module NattyUI
84
107
  *text,
85
108
  limit: max_width,
86
109
  bbcode: bbcode,
87
- ansi: Terminal.ansi?,
88
- ignore_newline: options[:eol] == false || options[:ignore_newline]
110
+ ansi: ansi,
111
+ ignore_newline: ignore_newline
89
112
  )
90
113
 
91
114
  if (align = options[:align]).nil?
92
115
  lines.each do |line|
93
- Terminal.print(prefix, line, suffix, EOL__, bbcode: false)
116
+ Terminal.print(prefix, line, suffix, @__eol, bbcode: false)
94
117
  @lines_written += 1
95
118
  prefix, prefix_next = prefix_next, nil if prefix_next
96
119
  end
@@ -110,7 +133,7 @@ module NattyUI
110
133
  ' ' * (max_width - width),
111
134
  line,
112
135
  suffix,
113
- EOL__,
136
+ @__eol,
114
137
  bbcode: false
115
138
  )
116
139
  @lines_written += 1
@@ -125,7 +148,7 @@ module NattyUI
125
148
  line,
126
149
  ' ' * (space - lw),
127
150
  suffix,
128
- EOL__,
151
+ @__eol,
129
152
  bbcode: false
130
153
  )
131
154
  @lines_written += 1
@@ -138,7 +161,7 @@ module NattyUI
138
161
  line,
139
162
  ' ' * (max_width - width),
140
163
  suffix,
141
- EOL__,
164
+ @__eol,
142
165
  bbcode: false
143
166
  )
144
167
  @lines_written += 1
@@ -148,6 +171,20 @@ module NattyUI
148
171
  self
149
172
  end
150
173
 
174
+ # Print given text with a decoration mark.
175
+ #
176
+ # @param text (see puts)
177
+ # @param mark [Symbol, #to_s]
178
+ # marker type
179
+ #
180
+ # @return (see puts)
181
+ def mark(*text, mark: :default, **options)
182
+ mark = Theme.current.mark(mark)
183
+ options[:first_line_prefix] = mark
184
+ options[:first_line_prefix_width] = mark.width
185
+ puts(*text, **options)
186
+ end
187
+
151
188
  # Print given text as lines like {#puts}. Used in elements with temporary
152
189
  # output like {#task} the text will be kept ("pinned").
153
190
  #
@@ -169,21 +206,7 @@ module NattyUI
169
206
  #
170
207
  # @return (see puts)
171
208
  def pin(*text, mark: nil, **options)
172
- options[:pin] = true
173
- options[:first_line_prefix] = Theme.current.mark(mark) if mark
174
- puts(*text, **options)
175
- end
176
-
177
- # Print given text with a decoration mark.
178
- #
179
- # @param text (see puts)
180
- # @param mark [Symbol, #to_s]
181
- # marker type
182
- #
183
- # @return (see puts)
184
- def mark(*text, mark: :default)
185
- mark = Theme.current.mark(mark)
186
- puts(*text, first_line_prefix: mark, first_line_prefix_width: mark.width)
209
+ mark(*text, mark: mark, pin: true, **options)
187
210
  end
188
211
 
189
212
  # Print given text as a quotation.
@@ -198,7 +221,7 @@ module NattyUI
198
221
  *text,
199
222
  prefix: quote,
200
223
  prefix_width: quote.width,
201
- max_width: width < 20 ? nil : width.to_i
224
+ max_width: width < 20 ? nil : width.round
202
225
  )
203
226
  end
204
227
 
@@ -293,7 +316,7 @@ module NattyUI
293
316
  #
294
317
  # @return (see puts)
295
318
  def space(count = 1)
296
- puts("\n" * count) if (count = count.to_i).positive?
319
+ (count = count.to_i).positive? ? puts("\n" * count) : self
297
320
  end
298
321
 
299
322
  # Print given items as list (like 'ls' command).
@@ -735,7 +758,8 @@ module NattyUI
735
758
  end
736
759
  end
737
760
 
738
- # Request a user's chocie.
761
+ # Allows the user to select an option from a selection.
762
+ # The selected option is returned.
739
763
  #
740
764
  # @overload choice(*choices, abortable: false)
741
765
  # @param [#to_s] choices
@@ -773,6 +797,8 @@ module NattyUI
773
797
  # one or more alternatives to select from
774
798
  # @param [true, false] abortable
775
799
  # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
800
+ # @param [#to_s, nil] selected
801
+ # optionally pre-selected option
776
802
  #
777
803
  # @return [Object]
778
804
  # key for selected choice
@@ -795,6 +821,8 @@ module NattyUI
795
821
  # one or more alternatives to select from
796
822
  # @param [true, false] abortable
797
823
  # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
824
+ # @param [Integer] selected
825
+ # pre-selected option index
798
826
  #
799
827
  # @yieldparam temp [Temporary]
800
828
  # temporary displayed section (section will be erased after input)
@@ -807,17 +835,79 @@ module NattyUI
807
835
  def choice(*choices, abortable: false, selected: nil, **kwchoices, &block)
808
836
  return if choices.empty? && kwchoices.empty?
809
837
  choice =
810
- case NattyUI.input_mode
811
- when :default
838
+ if Terminal.ansi?
812
839
  Choice.new(self, choices, kwchoices, abortable, selected)
813
- when :dumb
814
- DumbChoice.new(self, choices, kwchoices, abortable)
815
840
  else
816
- return
841
+ DumbChoice.new(self, choices, kwchoices, abortable)
817
842
  end
818
843
  __with(choice) { choice.select(&block) }
819
844
  end
820
845
 
846
+ # Allows the user to select from several options.
847
+ # All options are returned with their selection status.
848
+ #
849
+ # @param [{#to_s => [true,false]}] choices
850
+ # Hash of options and their selection state
851
+ # @param [true, false] abortable
852
+ # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
853
+ # @param [#to_s, nil] selected
854
+ # optionally pre-selected key
855
+ #
856
+ # @yieldparam temp [Temporary]
857
+ # temporary displayed section (section will be erased after input)
858
+ #
859
+ # @return [{#to_s => [true,false]}]
860
+ # Hash of options and their selection state
861
+ # @return [nil]
862
+ # when user aborted the selection
863
+ def options(abortable: false, selected: nil, **choices, &block)
864
+ return {} if choices.empty?
865
+ options =
866
+ if Terminal.ansi?
867
+ Options.new(self, choices, abortable, selected)
868
+ else
869
+ DumbOptions.new(self, choices, abortable, selected)
870
+ end
871
+ __with(options) { options.select(&block) }
872
+ end
873
+
874
+ # Allows the user to select from several options.
875
+ # The selected options are returned.
876
+ #
877
+ # @example Select a terminal
878
+ # ui.select %w[Kitty iTerm2 Ghostty Tabby Rio] do
879
+ # ui.puts '[i]Which terminal applications did you already tested?[/i]'
880
+ # end
881
+ #
882
+ # @param [Array<#to_s>] choices
883
+ # selectable options
884
+ # @param [true, false] abortable
885
+ # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
886
+ # @param [Integer, :all, nil] selected
887
+ # optionally pre-selected option index or `:all` to pre-select all items
888
+ # @yieldparam temp [Temporary]
889
+ # temporary displayed section (section will be erased after input)
890
+ #
891
+ # @return [Array<#to_s>]
892
+ # selected options
893
+ # @return [nil]
894
+ # when user aborted the selection
895
+ def select(*choices, abortable: false, selected: nil, &block)
896
+ return [] if choices.empty?
897
+ choices = choices[0] if choices.size == 1 && choices[0].is_a?(Enumerable)
898
+ if selected == :all
899
+ sel = true
900
+ elsif selected
901
+ selected = choices[selected.to_i]
902
+ end
903
+ options(
904
+ abortable: abortable,
905
+ selected: selected,
906
+ **choices.to_h { [_1, sel] },
907
+ &block
908
+ ).filter_map { |key, selected| key if selected }
909
+ end
910
+
821
911
  #
822
912
  # @!endgroup
823
913
  #
@@ -870,18 +960,17 @@ module NattyUI
870
960
  end
871
961
  end
872
962
  end
873
-
874
- EOL__ = Terminal.ansi? ? "\e[m\n" : "\n"
875
- private_constant :EOL__
876
963
  end
877
964
 
878
965
  dir = __dir__
879
966
  autoload :Choice, "#{dir}/choice.rb"
880
967
  autoload :DumbChoice, "#{dir}/dumb_choice.rb"
968
+ autoload :DumbOptions, "#{dir}/dumb_options.rb"
881
969
  autoload :CompactLSRenderer, "#{dir}/ls_renderer.rb"
882
970
  autoload :Framed, "#{dir}/framed.rb"
883
971
  autoload :HBarsRenderer, "#{dir}/hbars_renderer.rb"
884
972
  autoload :LSRenderer, "#{dir}/ls_renderer.rb"
973
+ autoload :Options, "#{dir}/options.rb"
885
974
  autoload :Progress, "#{dir}/progress.rb"
886
975
  autoload :DumbProgress, "#{dir}/progress.rb"
887
976
  autoload :Section, "#{dir}/section.rb"
@@ -895,10 +984,12 @@ module NattyUI
895
984
  private_constant(
896
985
  :Choice,
897
986
  :DumbChoice,
987
+ :DumbOptions,
898
988
  :CompactLSRenderer,
899
989
  :Framed,
900
990
  :HBarsRenderer,
901
991
  :LSRenderer,
992
+ :Options,
902
993
  :Progress,
903
994
  :DumbProgress,
904
995
  :Utils,
@@ -47,7 +47,6 @@ module NattyUI
47
47
 
48
48
  class Item
49
49
  attr_reader :width
50
-
51
50
  def to_s(in_width) = "#{@str}#{' ' * (in_width - @width)}"
52
51
 
53
52
  def initialize(str)
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element'
4
+
5
+ module NattyUI
6
+ class Options < Element
7
+ def select
8
+ yield(self) if block_given?
9
+ pin_line = NattyUI.lines_written
10
+ draw
11
+ last = @current
12
+ while (event = Terminal.read_key_event)
13
+ case event.name
14
+ when 'Esc', 'Ctrl+c'
15
+ return if @abortable
16
+ when 'Enter'
17
+ return @opts.transform_values(&:last)
18
+ when 'Space'
19
+ (last = @opts[@current])[-1] = !last[-1]
20
+ when 'a'
21
+ @opts.transform_values { _1[-1] = true }
22
+ last = nil
23
+ when 'n'
24
+ @opts.transform_values { _1[-1] = false }
25
+ last = nil
26
+ when 'Home'
27
+ @current = @opts.first.first
28
+ when 'End'
29
+ @current = @opts.keys.last
30
+ when 'Up', 'Back', 'Shift+Tab', 'i'
31
+ keys = @opts.keys
32
+ @current = keys[keys.index(@current) - 1]
33
+ when 'Down', 'Tab', 'k'
34
+ keys = @opts.keys
35
+ @current = keys[keys.index(@current) + 1] || keys[0]
36
+ end
37
+ next if last == @current
38
+ pin_line = NattyUI.back_to_line(pin_line, erase: false)
39
+ draw
40
+ last = @current
41
+ end
42
+ ensure
43
+ NattyUI.back_to_line(@start_line)
44
+ end
45
+
46
+ private
47
+
48
+ def initialize(parent, opts, abortable, selected)
49
+ super(parent)
50
+ @opts =
51
+ opts.to_h do |k, v|
52
+ v.is_a?(Enumerable) ? [k, [v[0], !!v[-1]]] : [k, [k, !!v]]
53
+ end
54
+ @abortable = abortable
55
+ @current = @opts.key?(selected) ? selected : @opts.first.first
56
+ @start_line = NattyUI.lines_written
57
+ theme = Theme.current
58
+ @style = {
59
+ false => theme.choice_style,
60
+ true => theme.choice_current_style
61
+ }.compare_by_identity
62
+ end
63
+
64
+ def draw
65
+ states = NattyUI::Theme.current.option_states
66
+ @opts.each_pair do |key, (str, selected)|
67
+ mark = states.dig(current = key == @current, selected)
68
+ @parent.puts(
69
+ "#{@style[current]}#{str}",
70
+ first_line_prefix: mark,
71
+ first_line_prefix_width: mark.width
72
+ )
73
+ end
74
+ end
75
+ end
76
+ private_constant :Options
77
+ end
data/lib/natty-ui/task.rb CHANGED
@@ -11,7 +11,7 @@ module NattyUI
11
11
  private
12
12
 
13
13
  def _done(text)
14
- NattyUI.back_to_line(@start_line, erase: :all)
14
+ NattyUI.back_to_line(@start_line)
15
15
  @start_line = nil
16
16
  cm = Theme.current.mark(:checkmark)
17
17
  @parent.puts(
@@ -8,11 +8,10 @@ module NattyUI
8
8
  #
9
9
  class Temporary < Element
10
10
  # @!visibility private
11
- def puts(*objects, **options)
11
+ def puts(*objects, **opts)
12
12
  return self if @state
13
- if options.delete(:pin)
14
- @pins ||= []
15
- @pins << [objects, options.except(:prefix_width, :suffix_width)]
13
+ if opts.delete(:pin)
14
+ (@pins ||= []) << [objects, opts.except(:prefix_width, :suffix_width)]
16
15
  end
17
16
  super
18
17
  end
@@ -20,8 +19,8 @@ module NattyUI
20
19
  # @!visibility private
21
20
  def done
22
21
  return self if @state
23
- NattyUI.back_to_line(@start_line, erase: :all) if @start_line
24
- @pins&.each { |objects, options| puts(*objects, **options) }
22
+ NattyUI.back_to_line(@start_line) if @start_line
23
+ @pins&.each { |objects, opts| puts(*objects, **opts) }
25
24
  @state = :ok
26
25
  self
27
26
  end
@@ -55,7 +55,7 @@ module NattyUI
55
55
  failed: '[bright_red]𝑭[/fg]',
56
56
  current: '[bright_green]➔[/fg]',
57
57
  choice: '[bright_white]◦[/fg]',
58
- current_choice: '[bright_green][/fg]'
58
+ current_choice: '[bright_green][/fg]'
59
59
  )
60
60
  theme.define_section(
61
61
  default: :bright_blue,
@@ -166,11 +166,13 @@ module NattyUI
166
166
  end
167
167
 
168
168
  class Compiled
169
- attr_reader :task_style, :choice_current_style, :choice_style
169
+ attr_reader :task_style,
170
+ :choice_current_style,
171
+ :choice_style,
172
+ :option_states
170
173
 
171
174
  def defined_marks = @mark.keys.sort!
172
175
  def defined_borders = @border.keys.sort!
173
-
174
176
  def heading(index) = @heading[index.to_i.clamp(1, 6) - 1]
175
177
 
176
178
  def mark(value)
@@ -208,12 +210,25 @@ module NattyUI
208
210
  SectionBorder.create(border(theme.section_border)),
209
211
  theme.section_styles.dup.compare_by_identity
210
212
  )
213
+ @option_states = create_option_states
211
214
  end
212
215
 
213
216
  private
214
217
 
215
218
  def as_style(value) = (Ansi[*value].freeze if value)
216
219
 
220
+ def create_option_states
221
+ # [current?][selected?]
222
+ c = @mark[:current_choice]
223
+ n = @mark[:none]
224
+ sel = @mark[:checkmark]
225
+ uns = @mark[:choice]
226
+ {
227
+ false => { false => n + uns, true => n + sel }.compare_by_identity,
228
+ true => { false => c + uns, true => c + sel }.compare_by_identity
229
+ }.compare_by_identity.freeze
230
+ end
231
+
217
232
  def create_sections(template, styles)
218
233
  Hash
219
234
  .new do |h, kind|
@@ -224,7 +239,9 @@ module NattyUI
224
239
 
225
240
  def create_mark(mark)
226
241
  return {} if mark.empty?
227
- with_default(mark.to_h { |n, e| [n.to_sym, Str.new("#{e} ")] })
242
+ mark = mark.to_h { |n, e| [n.to_sym, Str.new("#{e} ")] }
243
+ mark[:none] ||= Str.new('  ', 2)
244
+ with_default(mark)
228
245
  end
229
246
 
230
247
  def create_border(border)
@@ -294,6 +311,7 @@ module NattyUI
294
311
 
295
312
  private_constant :SectionBorder
296
313
  end
314
+ # private_constant :Compiled
297
315
 
298
316
  private
299
317
 
@@ -3,7 +3,6 @@
3
3
  module NattyUI
4
4
  module Utils
5
5
  class << self
6
- # @!visibility private
7
6
  def style(value)
8
7
  value =
9
8
  case value
@@ -22,27 +21,22 @@ module NattyUI
22
21
  value.keep_if { Ansi.valid?(_1) }.empty? ? nil : value
23
22
  end
24
23
 
25
- # @!visibility private
26
24
  def align(value)
27
25
  POS_ALI.include?(value) ? value : :left
28
26
  end
29
27
 
30
- # @!visibility private
31
28
  def position(value)
32
29
  value if POS_ALI.include?(value)
33
30
  end
34
31
 
35
- # @!visibility private
36
32
  def vertical(value)
37
33
  VERT.include?(value) ? value : :top
38
34
  end
39
35
 
40
- # @!visibility private
41
36
  def split_table_attr(values)
42
37
  [values.slice(*TAB_ATTR), values.except(*TAB_ATTR)]
43
38
  end
44
39
 
45
- # @!visibility private
46
40
  def padding(*value)
47
41
  value = value.flatten.take(4).map! { [0, _1.to_i].max }
48
42
  case value.size
@@ -60,7 +54,6 @@ module NattyUI
60
54
  end
61
55
  alias margin padding
62
56
 
63
- # @!visibility private
64
57
  def as_size(range, value)
65
58
  return range.begin if value == :min
66
59
  return range.end if value.nil? || value.is_a?(Symbol)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module NattyUI
4
4
  # The version number of the gem.
5
- VERSION = '0.28.0'
5
+ VERSION = '0.30.0'
6
6
  end
data/lib/natty-ui.rb CHANGED
@@ -36,10 +36,8 @@ module NattyUI
36
36
  # when terminal inoput is not supported
37
37
  def input_mode
38
38
  case Terminal.input_mode
39
- when :csi_u
39
+ when :csi_u, :legacy
40
40
  :default
41
- when :legacy
42
- Terminal.ansi? ? :default : :dumb
43
41
  when :dumb
44
42
  :dumb
45
43
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: natty-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.0
4
+ version: 0.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0.12'
18
+ version: 0.13.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '0.12'
25
+ version: 0.13.0
26
26
  description: |
27
27
  This is the beautiful, nice, nifty, fancy, neat, pretty, cool, rich, lovely,
28
28
  natty user interface tool you like to have for your command line applications.
@@ -50,6 +50,7 @@ files:
50
50
  - examples/key-codes.rb
51
51
  - examples/ls.rb
52
52
  - examples/named-colors.rb
53
+ - examples/options.rb
53
54
  - examples/sections.rb
54
55
  - examples/tables.rb
55
56
  - examples/tasks.rb
@@ -58,11 +59,13 @@ files:
58
59
  - lib/natty-ui/attributes.rb
59
60
  - lib/natty-ui/choice.rb
60
61
  - lib/natty-ui/dumb_choice.rb
62
+ - lib/natty-ui/dumb_options.rb
61
63
  - lib/natty-ui/element.rb
62
64
  - lib/natty-ui/features.rb
63
65
  - lib/natty-ui/framed.rb
64
66
  - lib/natty-ui/hbars_renderer.rb
65
67
  - lib/natty-ui/ls_renderer.rb
68
+ - lib/natty-ui/options.rb
66
69
  - lib/natty-ui/progress.rb
67
70
  - lib/natty-ui/section.rb
68
71
  - lib/natty-ui/table.rb
@@ -75,15 +78,15 @@ files:
75
78
  - lib/natty-ui/version.rb
76
79
  - lib/natty-ui/width_finder.rb
77
80
  - lib/natty_ui.rb
78
- homepage: https://github.com/mblumtritt/natty-ui
81
+ homepage: https://codeberg.org/mblumtritt/natty-ui
79
82
  licenses:
80
83
  - BSD-3-Clause
81
84
  metadata:
85
+ source_code_uri: https://codeberg.org/mblumtritt/natty-ui
86
+ bug_tracker_uri: https://codeberg.org/mblumtritt/natty-ui/issues
87
+ documentation_uri: https://rubydoc.info/gems/natty-ui
82
88
  rubygems_mfa_required: 'true'
83
89
  yard.run: yard
84
- source_code_uri: https://github.com/mblumtritt/natty-ui
85
- bug_tracker_uri: https://github.com/mblumtritt/natty-ui/issues
86
- documentation_uri: https://rubydoc.info/gems/natty-ui/NattyUI
87
90
  rdoc_options: []
88
91
  require_paths:
89
92
  - lib