diff --git a/examples/multi_with_inset.rb b/examples/multi_with_inset.rb new file mode 100644 index 0000000..01f6e93 --- /dev/null +++ b/examples/multi_with_inset.rb @@ -0,0 +1,26 @@ +# encoding: utf-8 + +require 'tty-spinner' + +spinners = TTY::Spinner::Multi.new(message: "[:spinner] Top level spinner") + +sp1 = spinners.register "[:spinner] one" +sp2 = spinners.register "[:spinner] two" +sp3 = spinners.register "[:spinner] three" + +spinners.auto_spin +sp1.auto_spin +sp2.auto_spin +sp3.auto_spin + +sleep(2) + +sp1.success +sleep 1 +sp2.success + +sleep 1 + +sp3.error + +spinners.error diff --git a/lib/tty/spinner.rb b/lib/tty/spinner.rb index dfc46e7..19b4eb2 100644 --- a/lib/tty/spinner.rb +++ b/lib/tty/spinner.rb @@ -272,7 +272,7 @@ def stop(stop_message = '') end write(data, true) - write("\n", false) unless @clear + write("\n", false) unless @clear || @multispinner ensure @state = :stopped @done = true @@ -354,7 +354,7 @@ def execute_on_line @first_run = false else output.print TTY::Cursor.save - output.print TTY::Cursor.up lines_up + output.print TTY::Cursor.up(lines_up) yield if block_given? output.print TTY::Cursor.restore end @@ -372,7 +372,11 @@ def execute_on_line def write(data, clear_first = false) execute_on_line do output.print(ECMA_CSI + '1' + ECMA_CHA) if clear_first - output.print(data) + + # If there's a top level spinner, print with inset + characters_in = @multispinner.nil? ? "" : @multispinner.line_inset(self) + + output.print(characters_in + data) output.flush end end diff --git a/lib/tty/spinner/multi.rb b/lib/tty/spinner/multi.rb index b5b43d0..19f51ef 100644 --- a/lib/tty/spinner/multi.rb +++ b/lib/tty/spinner/multi.rb @@ -18,11 +18,16 @@ class Multi def_delegators :@spinners, :each, :empty?, :length def initialize(options = {}) + message = options.delete(:message) @options = options @create_spinner_lock = Mutex.new @spinners = [] + unless message.nil? + @top_level_spinner = register(message) + end + @callbacks = { success: [], error: [], @@ -47,6 +52,18 @@ def register(pattern, options = {}) spinner end + def top_level_spinner + raise "No top level spinner" if @top_level_spinner.nil? + + @top_level_spinner + end + + def auto_spin + raise "No top level spinner" if @top_level_spinner.nil? + + @top_level_spinner.auto_spin + end + # Find relative offset position to which to move the current cursor # # The position is found among the registered spinners given the current @@ -69,6 +86,16 @@ def count_line_offset(index) end end + # Find the number of characters to move into the line before printing the + # spinner + # + # @api public + def line_inset(spinner) + return " " if @top_level_spinner && spinner != @top_level_spinner + + "" + end + # Check if all spinners are done # # @return [Boolean] @@ -108,6 +135,7 @@ def stop # # @api public def success + @top_level_spinner.success if @top_level_spinner @spinners.dup.each(&:success) emit :success end @@ -116,6 +144,7 @@ def success # # @api public def error + @top_level_spinner.error if @top_level_spinner @spinners.dup.each(&:error) emit :error end diff --git a/spec/unit/multi/top_level_spinner_spec.rb b/spec/unit/multi/top_level_spinner_spec.rb new file mode 100644 index 0000000..c884a21 --- /dev/null +++ b/spec/unit/multi/top_level_spinner_spec.rb @@ -0,0 +1,57 @@ +# coding: utf-8 +# + +RSpec.describe TTY::Spinner::Multi, '#line_inset' do + let(:output) { StringIO.new('', 'w+') } + + it "returns the empty string when there's no top level spinner" do + spinners = TTY::Spinner::Multi.new(output: output) + allow_any_instance_of(TTY::Spinner).to receive(:add_multispinner) + + spinner = spinners.register "" + + expect(spinners.line_inset(spinner)).to eq('') + end + + it "returns the empty string for the top level spinner" do + spinners = TTY::Spinner::Multi.new(output: output, message: "Top level spinner") + allow_any_instance_of(TTY::Spinner).to receive(:add_multispinner) + + spinners.register "" + + expect(spinners.line_inset(spinners.top_level_spinner)).to eq('') + end + + it "returns four spaces when there is a top level spinner" do + spinners = TTY::Spinner::Multi.new(output: output, message: "Top level spinner") + allow_any_instance_of(TTY::Spinner).to receive(:add_multispinner) + + spinner = spinners.register "" + + expect(spinners.line_inset(spinner)).to eq(' ') + end +end + +RSpec.describe TTY::Spinner::Multi, '#auto_spin' do + let(:output) { StringIO.new('', 'w+') } + + it "raises and exception when #auto_spin is called without a top level spinner" do + spinners = TTY::Spinner::Multi.new(output: output) + allow_any_instance_of(TTY::Spinner).to receive(:add_multispinner) + + spinners.register "" + + expect { spinners.auto_spin }.to raise_exception + end + + it "doesn't raise exception" do + spinners = TTY::Spinner::Multi.new(output: output, message: "Top level spinner") + allow_any_instance_of(TTY::Spinner).to receive(:add_multispinner) + allow_any_instance_of(TTY::Spinner).to receive(:auto_spin) + + spinners.register "" + + expect { spinners.auto_spin }.not_to raise_exception + end + +end diff --git a/spec/unit/spin_spec.rb b/spec/unit/spin_spec.rb index f268b0b..d050608 100644 --- a/spec/unit/spin_spec.rb +++ b/spec/unit/spin_spec.rb @@ -47,6 +47,7 @@ it "spins with newline when it has a MultiSpinner" do multi_spinner = double("MultiSpinner") allow(multi_spinner).to receive(:count_line_offset).and_return(1, 1, 2, 1) + allow(multi_spinner).to receive(:line_inset).and_return("") spinner = TTY::Spinner.new(output: output, interval: 100) spinner.add_multispinner(multi_spinner, 0) @@ -54,6 +55,9 @@ spinner2 = TTY::Spinner.new(output: output, interval: 100) spinner2.add_multispinner(multi_spinner, 1) + save = Gem.win_platform? ? "\e[s" : "\e7" + restore = Gem.win_platform? ? "\e[u" : "\e8" + spinner.spin spinner2.spin output.rewind @@ -66,10 +70,10 @@ expect(output.read).to eq([ "\e[1G|\n", "\e[1G|\n", - "\e[s", # save position + save, "\e[2A", # up 2 lines "\e[1G/", - "\e[u" # restore position + restore ].join) spinner2.spin @@ -77,14 +81,14 @@ expect(output.read).to eq([ "\e[1G|\n", "\e[1G|\n", - "\e[s", # save position + save, "\e[2A", # up 2 lines "\e[1G/", - "\e[u", # restore position - "\e[s", # save position + restore, + save, "\e[1A", # up 1 line "\e[1G/", - "\e[u" # restore position + restore ].join) end diff --git a/tty-spinner.gemspec b/tty-spinner.gemspec index 9069954..0406ffe 100644 --- a/tty-spinner.gemspec +++ b/tty-spinner.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |spec| spec.files = Dir['lib/**/*.rb', 'LICENSE.txt', 'README.md'] spec.require_paths = ['lib'] - spec.add_runtime_dependency 'tty-cursor' + spec.add_runtime_dependency 'tty-cursor', '>= 0.5.0' spec.add_development_dependency 'bundler', '>= 1.5.0', '< 2.0' spec.add_development_dependency 'rake' end