From c5ea79d5cecea9cae6ad0c1f31703a98cd329431 Mon Sep 17 00:00:00 2001 From: aycabta Date: Sat, 29 Aug 2020 20:48:25 +0900 Subject: [PATCH 1/3] Add OMIT_ON_ASSIGNMENT Omit the results evaluated at assignment if they are too long. The behavior of ECHO_ON_ASSIGNMENT being on by default is hard to understand, so I change it to off by default. Instead, we turn OMIT_ON_ASSIGNMENT on by default. The result is displayed on assignment, but it will always be short and within one line of the screen. --- lib/irb.rb | 26 +++++++++++- lib/irb/context.rb | 28 +++++++++++-- lib/irb/init.rb | 5 +++ lib/irb/input-method.rb | 9 +++++ test/irb/test_context.rb | 85 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 143 insertions(+), 10 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index ed2b336b3..e020aa6f3 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -10,6 +10,7 @@ # # require "ripper" +require "reline" require_relative "irb/init" require_relative "irb/context" @@ -538,7 +539,15 @@ def eval_input begin line.untaint if RUBY_VERSION < '2.7' @context.evaluate(line, line_no, exception: exc) - output_value if @context.echo? && (@context.echo_on_assignment? || !assignment_expression?(line)) + if @context.echo? + if assignment_expression?(line) + if @context.echo_on_assignment? + output_value(@context.omit_on_assignment?) + end + else + output_value + end + end rescue Interrupt => exc rescue SystemExit, SignalException raise @@ -737,9 +746,22 @@ def prompt(prompt, ltype, indent, line_no) # :nodoc: p end - def output_value # :nodoc: + def output_value(omit = false) # :nodoc: str = @context.inspect_last_value multiline_p = str.include?("\n") + if omit + if multiline_p + str.gsub!(/(\A.*?\n).*/m, "\\1...") + else + winwidth = @context.io.winsize.last + output_width = Reline::Unicode.calculate_width(@context.return_format % str, true) + diff_size = output_width - Reline::Unicode.calculate_width(str, true) + if diff_size.positive? and output_width > winwidth + lines, _ = Reline::Unicode.split_by_width(str, winwidth - diff_size - 3) + str = "%s...\e[0m" % lines.first + end + end + end if multiline_p && @context.newline_before_multiline_output? printf @context.return_format, "\n#{str}" else diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 4f5460a84..4f001729e 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -131,7 +131,12 @@ def initialize(irb, workspace = nil, input_method = nil) @echo_on_assignment = IRB.conf[:ECHO_ON_ASSIGNMENT] if @echo_on_assignment.nil? - @echo_on_assignment = false + @echo_on_assignment = true + end + + @omit_on_assignment = IRB.conf[:OMIT_ON_ASSIGNMENT] + if @omit_on_assignment.nil? + @omit_on_assignment = true end @newline_before_multiline_output = IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] @@ -251,13 +256,27 @@ def main attr_accessor :echo # Whether to echo for assignment expressions # - # Uses IRB.conf[:ECHO_ON_ASSIGNMENT] if available, or defaults to +false+. + # Uses IRB.conf[:ECHO_ON_ASSIGNMENT] if available, or defaults to +true+. # # a = "omg" - # IRB.CurrentContext.echo_on_assignment = true - # a = "omg" # #=> omg + # IRB.CurrentContext.echo_on_assignment = false + # a = "omg" attr_accessor :echo_on_assignment + # Whether to omit echo for assignment expressions + # + # Uses IRB.conf[:OMIT_ON_ASSIGNMENT] if available, or defaults to +true+. + # + # a = [1] * 10 + # #=> [1, 1, 1, 1, 1, 1, 1, 1, ... + # [1] * 10 + # #=> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + # IRB.CurrentContext.omit_on_assignment = false + # a = [1] * 10 + # #=> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + # [1] * 10 + # #=> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + attr_accessor :omit_on_assignment # Whether a newline is put before multiline output. # # Uses IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] if available, @@ -306,6 +325,7 @@ def main alias ignore_eof? ignore_eof alias echo? echo alias echo_on_assignment? echo_on_assignment + alias omit_on_assignment? omit_on_assignment alias newline_before_multiline_output? newline_before_multiline_output # Returns whether messages are displayed or not. diff --git a/lib/irb/init.rb b/lib/irb/init.rb index da40bee5e..44383609b 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -52,6 +52,7 @@ def IRB.init_config(ap_path) @CONF[:IGNORE_EOF] = false @CONF[:ECHO] = nil @CONF[:ECHO_ON_ASSIGNMENT] = nil + @CONF[:OMIT_ON_ASSIGNMENT] = nil @CONF[:VERBOSE] = nil @CONF[:EVAL_HISTORY] = nil @@ -177,6 +178,10 @@ def IRB.parse_opts(argv: ::ARGV) @CONF[:ECHO_ON_ASSIGNMENT] = true when "--noecho-on-assignment" @CONF[:ECHO_ON_ASSIGNMENT] = false + when "--omit-on-assignment" + @CONF[:OMIT_ON_ASSIGNMENT] = true + when "--noomit-on-assignment" + @CONF[:OMIT_ON_ASSIGNMENT] = false when "--verbose" @CONF[:VERBOSE] = true when "--noverbose" diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 7cb211354..6e8748875 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -12,6 +12,7 @@ require_relative 'src_encoding' require_relative 'magic-file' require_relative 'completion' +require 'io/console' require 'reline' module IRB @@ -36,6 +37,14 @@ def gets end public :gets + def winsize + if instance_variable_defined?(:@stdout) + @stdout.winsize + else + [24, 80] + end + end + # Whether this input method is still readable when there is no more data to # read. # diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 55bd6ff63..3a4c98729 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -30,6 +30,10 @@ def encoding def reset @line_no = 0 end + + def winsize + [10, 20] + end end def setup @@ -213,6 +217,75 @@ def test_echo_on_assignment assert_equal("", out) end + def test_omit_on_assignment + input = TestInputMethod.new([ + "a = [1] * 100\n", + "a\n", + ]) + value = [1] * 100 + irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) + irb.context.return_format = "=> %s\n" + + irb.context.echo = true + irb.context.echo_on_assignment = false + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("=> #{value.inspect}\n", out) + + input.reset + irb.context.echo = true + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("=> #{value.inspect[0..(input.winsize.last - 9)]}...\e[0m\n=> #{value.inspect}\n", out) + + input.reset + irb.context.echo = true + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = false + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("=> #{value.inspect}\n=> #{value.inspect}\n", out) + + input.reset + irb.context.echo = false + irb.context.echo_on_assignment = false + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("", out) + + input.reset + irb.context.echo = false + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("", out) + + input.reset + irb.context.echo = false + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = false + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("", out) + end + def test_echo_on_assignment_conf # Default IRB.conf[:ECHO] = nil @@ -221,22 +294,26 @@ def test_echo_on_assignment_conf irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) assert(irb.context.echo?, "echo? should be true by default") - refute(irb.context.echo_on_assignment?, "echo_on_assignment? should be false by default") + assert(irb.context.echo_on_assignment?, "echo_on_assignment? should be true by default") + assert(irb.context.omit_on_assignment?, "omit_on_assignment? should be true by default") # Explicitly set :ECHO to false IRB.conf[:ECHO] = false irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) refute(irb.context.echo?, "echo? should be false when IRB.conf[:ECHO] is set to false") - refute(irb.context.echo_on_assignment?, "echo_on_assignment? should be false by default") + assert(irb.context.echo_on_assignment?, "echo_on_assignment? should be true by default") + assert(irb.context.omit_on_assignment?, "omit_on_assignment? should be true by default") # Explicitly set :ECHO_ON_ASSIGNMENT to true IRB.conf[:ECHO] = nil - IRB.conf[:ECHO_ON_ASSIGNMENT] = true + IRB.conf[:ECHO_ON_ASSIGNMENT] = false + IRB.conf[:OMIT_ON_ASSIGNMENT] = false irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) assert(irb.context.echo?, "echo? should be true by default") - assert(irb.context.echo_on_assignment?, "echo_on_assignment? should be true when IRB.conf[:ECHO_ON_ASSIGNMENT] is set to true") + refute(irb.context.echo_on_assignment?, "echo_on_assignment? should be false when IRB.conf[:ECHO_ON_ASSIGNMENT] is set to false") + refute(irb.context.omit_on_assignment?, "omit_on_assignment? should be false when IRB.conf[:OMIT_ON_ASSIGNMENT] is set to false") end def test_multiline_output_on_default_inspector From 0feeae38c58420bac31bc3932f8d8790402c8bd5 Mon Sep 17 00:00:00 2001 From: aycabta Date: Thu, 3 Sep 2020 22:51:15 +0900 Subject: [PATCH 2/3] Omit output if first line of multiline is too long --- lib/irb.rb | 14 ++++++-- test/irb/test_context.rb | 76 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index e020aa6f3..d8e1209f2 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -750,10 +750,20 @@ def output_value(omit = false) # :nodoc: str = @context.inspect_last_value multiline_p = str.include?("\n") if omit + winwidth = @context.io.winsize.last if multiline_p - str.gsub!(/(\A.*?\n).*/m, "\\1...") + first_line = str.split("\n").first + result = @context.newline_before_multiline_output? ? (@context.return_format % first_line) : first_line + output_width = Reline::Unicode.calculate_width(result, true) + diff_size = output_width - Reline::Unicode.calculate_width(first_line, true) + if diff_size.positive? and output_width > winwidth + lines, _ = Reline::Unicode.split_by_width(first_line, winwidth - diff_size - 3) + str = "%s...\e[0m" % lines.first + multiline_p = false + else + str.gsub!(/(\A.*?\n).*/m, "\\1...") + end else - winwidth = @context.io.winsize.last output_width = Reline::Unicode.calculate_width(@context.return_format % str, true) diff_size = output_width - Reline::Unicode.calculate_width(str, true) if diff_size.positive? and output_width > winwidth diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 3a4c98729..fa628bba4 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -286,6 +286,82 @@ def test_omit_on_assignment assert_equal("", out) end + def test_omit_multiline_on_assignment + input = TestInputMethod.new([ + "class A; def inspect; ([?* * 1000] * 3).join(%{\\n}); end; end; a = A.new\n", + "a\n" + ]) + value = ([?* * 1000] * 3).join(%{\n}) + value_first_line = (?* * 1000).to_s + irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input) + irb.context.return_format = "=> %s\n" + + irb.context.echo = true + irb.context.echo_on_assignment = false + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("=> \n#{value}\n", out) + irb.context.evaluate('A.remove_method(:inspect)', 0) + + input.reset + irb.context.echo = true + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\e[0m\n=> \n#{value}\n", out) + irb.context.evaluate('A.remove_method(:inspect)', 0) + + input.reset + irb.context.echo = true + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = false + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("=> \n#{value}\n=> \n#{value}\n", out) + irb.context.evaluate('A.remove_method(:inspect)', 0) + + input.reset + irb.context.echo = false + irb.context.echo_on_assignment = false + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("", out) + irb.context.evaluate('A.remove_method(:inspect)', 0) + + input.reset + irb.context.echo = false + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = true + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("", out) + irb.context.evaluate('A.remove_method(:inspect)', 0) + + input.reset + irb.context.echo = false + irb.context.echo_on_assignment = true + irb.context.omit_on_assignment = false + out, err = capture_io do + irb.eval_input + end + assert_empty err + assert_equal("", out) + irb.context.evaluate('A.remove_method(:inspect)', 0) + end + def test_echo_on_assignment_conf # Default IRB.conf[:ECHO] = nil From c05bc9e59568532b55d6567fe94904986681527b Mon Sep 17 00:00:00 2001 From: aycabta Date: Mon, 14 Sep 2020 01:00:20 +0900 Subject: [PATCH 3/3] Need calculate_width and split_by_width of Reline::Unicode of reline 0.1.5 or later --- .travis.yml | 4 +++- irb.gemspec | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8bec342a..5878b331d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,6 @@ rvm: before_install: - yes | gem update --system -script: bundle exec rake test +script: + - bundle install + - bundle exec rake test diff --git a/irb.gemspec b/irb.gemspec index d3a4a26ba..af40a3245 100644 --- a/irb.gemspec +++ b/irb.gemspec @@ -78,7 +78,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = Gem::Requirement.new(">= 2.5") - spec.add_dependency "reline", ">= 0.0.1" + spec.add_dependency "reline", ">= 0.1.5" spec.add_development_dependency "bundler" spec.add_development_dependency "rake" end