natty-ui 0.27.0 → 0.29.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.
@@ -15,12 +15,12 @@ module NattyUI
15
15
  # Print given text as lines.
16
16
  #
17
17
  # @example Print two lines text, right aligned
18
- # ui.puts("Two lines", "of nice text", align: :right)
18
+ # ui.puts "Two lines", "of nice text", align: :right
19
19
  # # => Two lines
20
20
  # # => of nice text
21
21
  #
22
22
  # @example Print two lines text, with a prefix
23
- # ui.puts("Two lines", "of nice text", prefix: ': ')
23
+ # ui.puts "Two lines", "of nice text", prefix: ': '
24
24
  # # => : Two lines
25
25
  # # => : of nice text
26
26
  #
@@ -38,7 +38,16 @@ module NattyUI
38
38
  # itself
39
39
  def puts(*text, **options)
40
40
  bbcode = true if (bbcode = options[:bbcode]).nil?
41
+
41
42
  max_width = options[:max_width] || Terminal.columns
43
+ return self if max_width == 0
44
+ if max_width < 1
45
+ if max_width > 0
46
+ max_width *= Terminal.columns
47
+ elsif max_width < 0
48
+ max_width += Terminal.columns
49
+ end
50
+ end
42
51
 
43
52
  prefix_width =
44
53
  if (prefix = options[:prefix])
@@ -89,7 +98,7 @@ module NattyUI
89
98
 
90
99
  if (align = options[:align]).nil?
91
100
  lines.each do |line|
92
- Terminal.print(prefix, line, suffix, EOL__, bbcode: false)
101
+ Terminal.print(prefix, line, suffix, __eol, bbcode: false)
93
102
  @lines_written += 1
94
103
  prefix, prefix_next = prefix_next, nil if prefix_next
95
104
  end
@@ -109,7 +118,7 @@ module NattyUI
109
118
  ' ' * (max_width - width),
110
119
  line,
111
120
  suffix,
112
- EOL__,
121
+ __eol,
113
122
  bbcode: false
114
123
  )
115
124
  @lines_written += 1
@@ -124,7 +133,7 @@ module NattyUI
124
133
  line,
125
134
  ' ' * (space - lw),
126
135
  suffix,
127
- EOL__,
136
+ __eol,
128
137
  bbcode: false
129
138
  )
130
139
  @lines_written += 1
@@ -137,7 +146,7 @@ module NattyUI
137
146
  line,
138
147
  ' ' * (max_width - width),
139
148
  suffix,
140
- EOL__,
149
+ __eol,
141
150
  bbcode: false
142
151
  )
143
152
  @lines_written += 1
@@ -147,6 +156,20 @@ module NattyUI
147
156
  self
148
157
  end
149
158
 
159
+ # Print given text with a decoration mark.
160
+ #
161
+ # @param text (see puts)
162
+ # @param mark [Symbol, #to_s]
163
+ # marker type
164
+ #
165
+ # @return (see puts)
166
+ def mark(*text, mark: :default, **options)
167
+ mark = Theme.current.mark(mark)
168
+ options[:first_line_prefix] = mark
169
+ options[:first_line_prefix_width] = mark.width
170
+ puts(*text, **options)
171
+ end
172
+
150
173
  # Print given text as lines like {#puts}. Used in elements with temporary
151
174
  # output like {#task} the text will be kept ("pinned").
152
175
  #
@@ -168,21 +191,7 @@ module NattyUI
168
191
  #
169
192
  # @return (see puts)
170
193
  def pin(*text, mark: nil, **options)
171
- options[:pin] = true
172
- options[:first_line_prefix] = Theme.current.mark(mark) if mark
173
- puts(*text, **options)
174
- end
175
-
176
- # Print given text with a decoration mark.
177
- #
178
- # @param text (see puts)
179
- # @param mark [Symbol, #to_s]
180
- # marker type
181
- #
182
- # @return (see puts)
183
- def mark(*text, mark: :default)
184
- mark = Theme.current.mark(mark)
185
- puts(*text, first_line_prefix: mark, first_line_prefix_width: mark.width)
194
+ mark(*text, mark: mark, pin: true, **options)
186
195
  end
187
196
 
188
197
  # Print given text as a quotation.
@@ -197,7 +206,7 @@ module NattyUI
197
206
  *text,
198
207
  prefix: quote,
199
208
  prefix_width: quote.width,
200
- max_width: width < 20 ? nil : width.to_i
209
+ max_width: width < 20 ? nil : width.round
201
210
  )
202
211
  end
203
212
 
@@ -272,8 +281,8 @@ module NattyUI
272
281
 
273
282
  # Print a horizontal rule.
274
283
  #
275
- # @example
276
- # ui.hr(:heavy)
284
+ # @example Print double line
285
+ # ui.hr :double
277
286
  #
278
287
  # @param type [Symbol]
279
288
  # border type
@@ -305,10 +314,10 @@ module NattyUI
305
314
  # - any text as prefix
306
315
  #
307
316
  # @example Print all Ruby files as a numbered list
308
- # ui.ls(Dir['*/**/*.rb'], glyph: 1)
317
+ # ui.ls Dir['*/**/*.rb'], glyph: 1
309
318
  #
310
319
  # @example Print all Ruby files as a bullet point list (with green bullets)
311
- # ui.ls(Dir['*/**/*.rb'], glyph: '[green]•[/fg]')
320
+ # ui.ls Dir['*/**/*.rb'], glyph: '[green]•[/fg]'
312
321
  #
313
322
  # @param items [#to_s]
314
323
  # one or more convertible objects to list
@@ -416,6 +425,86 @@ module NattyUI
416
425
  table(**tab_att) { |table| table.add { _1.add(*text, **att) } }
417
426
  end
418
427
 
428
+ # Dump given values as vertical bars.
429
+ #
430
+ # @example Draw green bars
431
+ # ui.vbars 1..10, style: :green
432
+ #
433
+ # @example Draw very big bars
434
+ # ui.vbars 1..10, bar_width: 5, height: 20
435
+ #
436
+ # @param values [#to_a, Array<Numeric>] values to print
437
+ # @param normalize [true, false] whether the values should be normalized
438
+ # @param height [Integer] output height
439
+ # @param bar_width [:auto, :min, Integer] with of each bar
440
+ # @param style [Symbol, Array<Symbol>, nil] drawing style
441
+ #
442
+ # @raise [ArgumentError] if any value is negative
443
+ #
444
+ # @return (see puts)
445
+ def vbars(
446
+ values,
447
+ normalize: false,
448
+ height: 10,
449
+ bar_width: :auto,
450
+ style: nil
451
+ )
452
+ return self if (values = values.to_a).empty?
453
+ if values.any?(&:negative?)
454
+ raise(ArgumentError, 'values can not be negative')
455
+ end
456
+ puts(
457
+ *VBarsRenderer.lines(
458
+ values,
459
+ columns,
460
+ height,
461
+ normalize,
462
+ bar_width,
463
+ Terminal.ansi? ? style : nil
464
+ )
465
+ )
466
+ end
467
+
468
+ # Dump given values as horizontal bars.
469
+ #
470
+ # @example Draw green bars
471
+ # ui.hbars 1..10, style: :green
472
+ #
473
+ # @example Draw bars in half sreen width
474
+ # ui.hbars 1..10, style: :blue, width: 0.5
475
+ #
476
+ # @param values [#to_a, Array<Numeric>] values to print
477
+ # @param with_values [true, false] whether the values should be printed too
478
+ # @param normalize [true, false] whether the values should be normalized
479
+ # @param height [Integer] output height
480
+ # @param bar_width [:auto, :min, Integer] with of each bar
481
+ # @param style [Symbol, Array<Symbol>, nil] bar drawing style
482
+ # @param text_style [Symbol, Array<Symbol>, nil] text style
483
+ #
484
+ # @raise [ArgumentError] if any value is negative
485
+ #
486
+ # @return (see puts)
487
+ def hbars(
488
+ values,
489
+ with_values: true,
490
+ normalize: false,
491
+ width: :auto,
492
+ style: nil,
493
+ text_style: nil
494
+ )
495
+ return self if (values = values.to_a).empty?
496
+ if values.any?(&:negative?)
497
+ raise(ArgumentError, 'values can not be negative')
498
+ end
499
+ style = text_style = nil unless Terminal.ansi?
500
+ size = Utils.as_size(3..columns, width)
501
+ if with_values
502
+ puts(*HBarsRenderer.lines(values, size, normalize, style, text_style))
503
+ else
504
+ puts(*HBarsRenderer.lines_bars_only(values, size, normalize, style))
505
+ end
506
+ end
507
+
419
508
  # Dynamically display a task progress.
420
509
  # When a `max` parameter is given the progress will be displayed as a
421
510
  # progress bar below the `title`. Otherwise the progress is displayed just
@@ -434,15 +523,15 @@ module NattyUI
434
523
  # end
435
524
  #
436
525
  # @example Display simple progress
437
- # progress = ui.progress('Check some stuff')
526
+ # progress = ui.progress 'Check some stuff'
438
527
  # 10.times do
439
528
  # # simulate some work
440
- # sleep(0.1)
529
+ # sleep 0.1
441
530
  #
442
531
  # # here we actualize the progress
443
532
  # progress.step
444
533
  # end
445
- # progress.ok('Stuff checked ok')
534
+ # progress.ok 'Stuff checked ok'
446
535
  #
447
536
  # @overload progress(title, max: nil, pin: false)
448
537
  # @param title [#to_s]
@@ -491,10 +580,10 @@ module NattyUI
491
580
  #
492
581
  # @example
493
582
  # ui.section do |section|
494
- # section.h1('About Sections')
583
+ # section.h1 'About Sections'
495
584
  # section.space
496
- # section.puts('Sections are areas of text elements.')
497
- # section.puts('You can use any other feature inside such an area.')
585
+ # section.puts 'Sections are areas of text elements.'
586
+ # section.puts 'You can use any other feature inside such an area.'
498
587
  # end
499
588
  # # => ╭────╶╶╶
500
589
  # # => │ ╴╶╴╶─═══ About Sections ═══─╴╶╴╶
@@ -643,22 +732,19 @@ module NattyUI
643
732
  #
644
733
  # @return [true, false]
645
734
  # wheter the user inputs a positive result
735
+ # @return nil
736
+ # in error case
646
737
  #
647
738
  def await(yes: 'Enter', no: 'Esc')
648
- temporary do |arg|
649
- yield(arg) if block_given?
650
- while (key = Terminal.read_key)
651
- if (no == key) || (no.is_a?(Enumerable) && no.include?(key))
652
- return false
653
- end
654
- if (yes == key) || (yes.is_a?(Enumerable) && yes.include?(key))
655
- return true
656
- end
657
- end
739
+ return __await(yes, no) unless block_given?
740
+ temporary do |temp|
741
+ yield(temp)
742
+ __await(yes, no)
658
743
  end
659
744
  end
660
745
 
661
- # Request a user's chocie.
746
+ # Allows the user to select an option from a selection.
747
+ # The selected option is returned.
662
748
  #
663
749
  # @overload choice(*choices, abortable: false)
664
750
  # @param [#to_s] choices
@@ -696,6 +782,8 @@ module NattyUI
696
782
  # one or more alternatives to select from
697
783
  # @param [true, false] abortable
698
784
  # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
785
+ # @param [#to_s, nil] selected
786
+ # optionally pre-selected option
699
787
  #
700
788
  # @return [Object]
701
789
  # key for selected choice
@@ -718,6 +806,8 @@ module NattyUI
718
806
  # one or more alternatives to select from
719
807
  # @param [true, false] abortable
720
808
  # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
809
+ # @param [Integer] selected
810
+ # pre-selected option index
721
811
  #
722
812
  # @yieldparam temp [Temporary]
723
813
  # temporary displayed section (section will be erased after input)
@@ -727,20 +817,82 @@ module NattyUI
727
817
  # @return [nil]
728
818
  # when user aborted the selection
729
819
  #
730
- def choice(*choices, abortable: false, **kwchoices, &block)
820
+ def choice(*choices, abortable: false, selected: nil, **kwchoices, &block)
731
821
  return if choices.empty? && kwchoices.empty?
732
822
  choice =
733
- case NattyUI.input_mode
734
- when :default
735
- Choice.new(self, choices, kwchoices, abortable)
736
- when :dumb
737
- DumbChoice.new(self, choices, kwchoices, abortable)
823
+ if Terminal.ansi?
824
+ Choice.new(self, choices, kwchoices, abortable, selected)
738
825
  else
739
- return
826
+ DumbChoice.new(self, choices, kwchoices, abortable)
740
827
  end
741
828
  __with(choice) { choice.select(&block) }
742
829
  end
743
830
 
831
+ # Allows the user to select from several options.
832
+ # All options are returned with their selection status.
833
+ #
834
+ # @param [{#to_s => [true,false]}] choices
835
+ # Hash of options and their selection state
836
+ # @param [true, false] abortable
837
+ # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
838
+ # @param [#to_s, nil] selected
839
+ # optionally pre-selected key
840
+ #
841
+ # @yieldparam temp [Temporary]
842
+ # temporary displayed section (section will be erased after input)
843
+ #
844
+ # @return [{#to_s => [true,false]}]
845
+ # Hash of options and their selection state
846
+ # @return [nil]
847
+ # when user aborted the selection
848
+ def options(abortable: false, selected: nil, **choices, &block)
849
+ return {} if choices.empty?
850
+ options =
851
+ if Terminal.ansi?
852
+ Options.new(self, choices, abortable, selected)
853
+ else
854
+ DumbOptions.new(self, choices, abortable, selected)
855
+ end
856
+ __with(options) { options.select(&block) }
857
+ end
858
+
859
+ # Allows the user to select from several options.
860
+ # The selected options are returned.
861
+ #
862
+ # @example Select a terminal
863
+ # ui.select %w[Kitty iTerm2 Ghostty Tabby Rio] do
864
+ # ui.puts '[i]Which terminal applications did you already tested?[/i]'
865
+ # end
866
+ #
867
+ # @param [Array<#to_s>] choices
868
+ # selectable options
869
+ # @param [true, false] abortable
870
+ # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
871
+ # @param [Integer, :all, nil] selected
872
+ # optionally pre-selected option index or `:all` to pre-select all items
873
+ # @yieldparam temp [Temporary]
874
+ # temporary displayed section (section will be erased after input)
875
+ #
876
+ # @return [Array<#to_s>]
877
+ # selected options
878
+ # @return [nil]
879
+ # when user aborted the selection
880
+ def select(*choices, abortable: false, selected: nil, &block)
881
+ return [] if choices.empty?
882
+ choices = choices[0] if choices.size == 1 && choices[0].is_a?(Enumerable)
883
+ if selected == :all
884
+ sel = true
885
+ elsif selected
886
+ selected = choices[selected.to_i]
887
+ end
888
+ options(
889
+ abortable: abortable,
890
+ selected: selected,
891
+ **choices.to_h { [_1, sel] },
892
+ &block
893
+ ).filter_map { |key, selected| key if selected }
894
+ end
895
+
744
896
  #
745
897
  # @!endgroup
746
898
  #
@@ -757,7 +909,7 @@ module NattyUI
757
909
  #
758
910
  # @example Show tempoary information
759
911
  # ui.temporary do
760
- # ui.info('Information', 'This text will disappear when you pressed ENTER.')
912
+ # ui.info 'Information', 'This text will disappear when you pressed ENTER.'
761
913
  # ui.await
762
914
  # end
763
915
  #
@@ -783,16 +935,31 @@ module NattyUI
783
935
  __sec(color, "#{Theme.current.mark(color)}#{title}", text, &block)
784
936
  end
785
937
 
786
- EOL__ = Terminal.ansi? ? "\e[m\n" : "\n"
787
- private_constant :EOL__
938
+ def __await(yes, no)
939
+ while (event = Terminal.read_key_event&.name)
940
+ if (no == event) || (no.is_a?(Enumerable) && no.include?(event))
941
+ return false
942
+ end
943
+ if (yes == event) || (yes.is_a?(Enumerable) && yes.include?(event))
944
+ return true
945
+ end
946
+ end
947
+ end
948
+
949
+ def __eol
950
+ @__eol ||= Terminal.ansi? ? "\e[m\n" : "\n"
951
+ end
788
952
  end
789
953
 
790
954
  dir = __dir__
791
955
  autoload :Choice, "#{dir}/choice.rb"
792
- autoload :CompactLSRenderer, "#{dir}/ls_renderer.rb"
793
956
  autoload :DumbChoice, "#{dir}/dumb_choice.rb"
957
+ autoload :DumbOptions, "#{dir}/dumb_options.rb"
958
+ autoload :CompactLSRenderer, "#{dir}/ls_renderer.rb"
794
959
  autoload :Framed, "#{dir}/framed.rb"
960
+ autoload :HBarsRenderer, "#{dir}/hbars_renderer.rb"
795
961
  autoload :LSRenderer, "#{dir}/ls_renderer.rb"
962
+ autoload :Options, "#{dir}/options.rb"
796
963
  autoload :Progress, "#{dir}/progress.rb"
797
964
  autoload :DumbProgress, "#{dir}/progress.rb"
798
965
  autoload :Section, "#{dir}/section.rb"
@@ -801,6 +968,20 @@ module NattyUI
801
968
  autoload :Temporary, "#{dir}/temporary.rb"
802
969
  autoload :Theme, "#{dir}/theme.rb"
803
970
  autoload :Utils, "#{dir}/utils.rb"
804
-
805
- private_constant :Choice, :DumbChoice, :LSRenderer, :CompactLSRenderer
971
+ autoload :VBarsRenderer, "#{dir}/vbars_renderer.rb"
972
+
973
+ private_constant(
974
+ :Choice,
975
+ :DumbChoice,
976
+ :DumbOptions,
977
+ :CompactLSRenderer,
978
+ :Framed,
979
+ :HBarsRenderer,
980
+ :LSRenderer,
981
+ :Options,
982
+ :Progress,
983
+ :DumbProgress,
984
+ :Utils,
985
+ :VBarsRenderer
986
+ )
806
987
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'utils'
4
+
5
+ module NattyUI
6
+ module HBarsRenderer
7
+ class << self
8
+ def lines(vals, width, normalize, style, text_style)
9
+ if text_style
10
+ bots = Ansi[*text_style]
11
+ eots = Ansi::RESET
12
+ end
13
+ texts = vals.map { Str.new(_1) }
14
+ tw = texts.max_by(&:width).width
15
+ size = width - tw - 1
16
+ if size < 2 # text only
17
+ return texts.map { "#{bots}#{' ' * (tw - _1.width)}#{_1}#{eots}" }
18
+ end
19
+ if style
20
+ bos = Ansi[*style]
21
+ eos = Ansi::RESET
22
+ end
23
+ vals = normalize ? normalize!(vals, size) : adjust!(vals, size)
24
+ texts.map.with_index do |str, idx|
25
+ "#{bots}#{' ' * (tw - str.width)}#{str}#{eots}#{bos}▕#{
26
+ '▆' * vals[idx]
27
+ }#{eos}"
28
+ end
29
+ end
30
+
31
+ def lines_bars_only(vals, width, normalize, style)
32
+ if style
33
+ bos = Ansi[*style]
34
+ eos = Ansi::RESET
35
+ end
36
+ width -= 1
37
+ vals = normalize ? normalize!(vals, width) : adjust!(vals, width)
38
+ vals.map { "#{bos}▕#{'▆' * _1}#{eos}" }
39
+ end
40
+
41
+ private
42
+
43
+ def adjust!(vals, size)
44
+ max = vals.max.to_f
45
+ vals.map { ((_1 / max) * size).round }
46
+ end
47
+
48
+ def normalize!(vals, size)
49
+ min, max = vals.minmax
50
+ return Array.new(vals.size, size) if min == max
51
+ max = (max - min).to_f
52
+ vals.map { (((_1 - min) / max) * size).round }
53
+ end
54
+ end
55
+ end
56
+
57
+ private_constant :HBarsRenderer
58
+ end
@@ -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
@@ -156,6 +156,7 @@ module NattyUI
156
156
  ["#{@style}#{' ' * @padding[3]}", "#{' ' * @padding[1]}[/]"]
157
157
  end
158
158
  end
159
+ private_constant :Cell
159
160
  end
160
161
  private_constant :TableRenderer
161
162
  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