1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Backport lib/reline, and lib/irb for 3.0.1 2nd (#4157)

* [ruby/irb] Stub a screen size for tests

6663057083

* [ruby/irb] Support GitHub Actions

8e9e6c4037

* [ruby/irb] Stub a screen size for test_context

http://ci.rvm.jp/logfiles/brlog.trunk-random1.20210119-074232

ea87592d4a

* [ruby/irb] Use a real screen size for pp by default

9b9300dec2

* [ruby/irb] Rescue Errno::EINVAL on IRB pp

20210119T070008Z.log.html.gz
is caused by:

/export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline/ansi.rb:157:in `winsize': Invalid argument - <STDIN> (Errno::EINVAL)
        from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline/ansi.rb:157:in `get_screen_size'
        from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/reline.rb:168:in `get_screen_size'
        from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/forwardable.rb:238:in `get_screen_size'
        from /export/home/chkbuild/chkbuild-gcc/tmp/build/20210119T150010Z/ruby/lib/irb/color_printer.rb:7:in `pp'
        from -e:1:in `<main>'

1719514598

* [ruby/irb] Split test files for IRB::Color and IRB::ColorPrinter

d95e8daab3

* [ruby/irb] Undefine unused constants

eea9c16804

* [ruby/irb] Remove pp-specific stub from TestColor

because it was for TestColorPrinter

7569206fd4

* [ruby/irb] Delete a doodle-level memo comment...

fc3e1d9e0c

* [ruby/irb] Indent correctly with keyword "for" and "in"

47c83ea724

* [ruby/irb] Indent correctly with method calling with receiver

e7c68e74a0

* [ruby/irb] add `IRB::FileInputMethod.open` to ensure closing associated File

* tweak some methods not to raise exception after `#close`
* use it in `IRB::IrbLoader#{source_file,load_file}

ec2947acbd

* [ruby/irb] use `RubyLex::TerminateLineInput` appropriately [Bug #17564]

* using the appropriciate exception instead of `break` so that the session
  can be continue after the `irb_source` and `irb_load` commands
* suppress extra new line due to one more `#prompt` call

bdefaa7cfd

* [ruby/irb] specify the `VERBOSE` to `false` and fix tests to fit

502c590925

* In test, need to pass a context to IRB::WorkSpace.new explicitly

* Fix absolute path predicate on Windows

A path starts with '/' is not an absolute path on Windows, because
of drive letter or UNC.

* [ruby/irb] follow up the actual line number

7aed8fe3b1

* [ruby/irb] Add info.rb to gemspec

adbba19adf

* [ruby/irb] Allow "measure" command to take block

20f1ca23e9

* [ruby/irb] Enable to reassign a new block with "measure" command

b444573aa2

* [ruby/reline] Cache pasting state in processing a key

Because it's too slow.

The rendering time in IRB has been reduced as follows:

  start = Time.now

  def each_top_level_statement
    initialize_input
    catch(:TERM_INPUT) do
      loop do
        begin
          prompt
          unless l = lex
            throw :TERM_INPUT if @line == ''
          else
            @line_no += l.count("\n")
            next if l == "\n"
            @line.concat l
            if @code_block_open or @ltype or @continue or @indent > 0
              next
            end
          end
          if @line != "\n"
            @line.force_encoding(@io.encoding)
            yield @line, @exp_line_no
          end
          break if @io.eof?
          @line = ''
          @exp_line_no = @line_no

          @indent = 0
        rescue TerminateLineInput
          initialize_input
          prompt
        end
      end
    end
  end

  puts "Duration: #{Time.now - start} seconds"

0.22sec -> 0.14sec

b8b3dd52c0

* [ruby/reline] Initialize uninitialized variables in tests

25af4bb64b

* [ruby/reline] Remove an unused variable

123ea51166

* [ruby/reline] Scroll down when ^C is pressed

6877a7e3f5

* [ruby/reline] Show all lines higher than the screen when finished

On Unix-like OSes, logs prior to the screen are not editable. When the code
is higher than the screen, the code is only shown on the screen until input
is finished, but when it is finished, all lines are outputted.

8cd9132a39

* [ruby/reline] Handle past logs correctly when the code is higher than the screen

f197139b4a

* [ruby/reline] Update cursor info by inserting newline even if not in pasting

92d314f514

* [ruby/reline] Move cursor just after the last line when finished

ba06e4c480

* [ruby/reline] The vi_histedit supports multiline

This closes ruby/reline#253.

f131f86d71

* [ruby/reline] Autowrap correctly when inserting chars in the middle of a line

ebaf37255f

* [ruby/reline] Terminate correctly in the middle of lines higher than the screen

e1d9240ada

* [ruby/irb] Version 1.3.3

4c87035b7c

* [ruby/reline] Version 0.2.3

b26c7d60c8

Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
Co-authored-by: Nobuhiro IMAI <nov@yo.rim.or.jp>
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: ima1zumi <mariimaizumi5@gmail.com>
This commit is contained in:
aycabta 2021-02-07 21:04:32 +09:00 committed by GitHub
parent 0c6361ff28
commit 77700bf023
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 568 additions and 88 deletions

View file

@ -525,7 +525,7 @@ module IRB
printf "Use \"exit\" to leave %s\n", @context.ap_name
end
else
print "\n"
print "\n" if @context.prompting?
end
end
l

View file

@ -8,7 +8,7 @@ module IRB
super(*args)
end
def execute(type = nil, arg = nil)
def execute(type = nil, arg = nil, &block)
case type
when :off
IRB.conf[:MEASURE] = nil
@ -22,9 +22,15 @@ module IRB
added = IRB.set_measure_callback(type, arg)
puts "#{added[0]} is added." if added
else
IRB.conf[:MEASURE] = true
added = IRB.set_measure_callback(type, arg)
puts "#{added[0]} is added." if added
if block_given?
IRB.conf[:MEASURE] = true
added = IRB.set_measure_callback(&block)
puts "#{added[0]} is added." if added
else
IRB.conf[:MEASURE] = true
added = IRB.set_measure_callback(type, arg)
puts "#{added[0]} is added." if added
end
end
nil
end

View file

@ -15,9 +15,9 @@ module IRB
class Nop
def self.execute(conf, *opts)
def self.execute(conf, *opts, &block)
command = new(conf)
command.execute(*opts)
command.execute(*opts, &block)
end
def initialize(conf)

View file

@ -4,11 +4,21 @@ require 'irb/color'
module IRB
class ColorPrinter < ::PP
def self.pp(obj, out = $>, width = 79)
q = ColorPrinter.new(out, width)
q.guard_inspect_key {q.pp obj}
q.flush
out << "\n"
class << self
def pp(obj, out = $>, width = screen_width)
q = ColorPrinter.new(out, width)
q.guard_inspect_key {q.pp obj}
q.flush
out << "\n"
end
private
def screen_width
Reline.get_screen_size.last
rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN>
79
end
end
def text(str, width = nil)

View file

@ -31,8 +31,31 @@ module IRB # :nodoc:
load_file(path, priv)
end
if File.respond_to?(:absolute_path?)
def absolute_path?(path)
File.absolute_path?(path)
end
else
separator =
if File::ALT_SEPARATOR
File::SEPARATOR
else
"[#{Regexp.quote(File::SEPARATOR + File::ALT_SEPARATOR)}]"
end
ABSOLUTE_PATH_PATTERN = # :nodoc:
case Dir.pwd
when /\A\w:/, /\A#{separator}{2}/
/\A(?:\w:|#{separator})#{separator}/
else
/\A#{separator}/
end
def absolute_path?(path)
ABSOLUTE_PATH_PATTERN =~ path
end
end
def search_file_from_ruby_path(fn) # :nodoc:
if /^#{Regexp.quote(File::Separator)}/ =~ fn
if absolute_path?(fn)
return fn if File.exist?(fn)
return nil
end
@ -50,16 +73,18 @@ module IRB # :nodoc:
# See Irb#suspend_input_method for more information.
def source_file(path)
irb.suspend_name(path, File.basename(path)) do
irb.suspend_input_method(FileInputMethod.new(path)) do
|back_io|
irb.signal_status(:IN_LOAD) do
if back_io.kind_of?(FileInputMethod)
irb.eval_input
else
begin
FileInputMethod.open(path) do |io|
irb.suspend_input_method(io) do
|back_io|
irb.signal_status(:IN_LOAD) do
if back_io.kind_of?(FileInputMethod)
irb.eval_input
rescue LoadAbort
print "load abort!!\n"
else
begin
irb.eval_input
rescue LoadAbort
print "load abort!!\n"
end
end
end
end
@ -79,16 +104,18 @@ module IRB # :nodoc:
ws = WorkSpace.new
end
irb.suspend_workspace(ws) do
irb.suspend_input_method(FileInputMethod.new(path)) do
|back_io|
irb.signal_status(:IN_LOAD) do
if back_io.kind_of?(FileInputMethod)
irb.eval_input
else
begin
FileInputMethod.open(path) do |io|
irb.suspend_input_method(io) do
|back_io|
irb.signal_status(:IN_LOAD) do
if back_io.kind_of?(FileInputMethod)
irb.eval_input
rescue LoadAbort
print "load abort!!\n"
else
begin
irb.eval_input
rescue LoadAbort
print "load abort!!\n"
end
end
end
end

View file

@ -146,7 +146,7 @@ module IRB # :nodoc:
@CONF[:AT_EXIT] = []
end
def IRB.set_measure_callback(type = nil, arg = nil)
def IRB.set_measure_callback(type = nil, arg = nil, &block)
added = nil
if type
type_sym = type.upcase.to_sym
@ -155,6 +155,16 @@ module IRB # :nodoc:
end
elsif IRB.conf[:MEASURE_PROC][:CUSTOM]
added = [:CUSTOM, IRB.conf[:MEASURE_PROC][:CUSTOM], arg]
elsif block_given?
added = [:BLOCK, block, arg]
found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] }
if found
found[1] = block
return added
else
IRB.conf[:MEASURE_CALLBACKS] << added
return added
end
else
added = [:TIME, IRB.conf[:MEASURE_PROC][:TIME], arg]
end

View file

@ -124,10 +124,22 @@ module IRB
# Use a File for IO with irb, see InputMethod
class FileInputMethod < InputMethod
class << self
def open(file, &block)
begin
io = new(file)
block.call(io)
ensure
io&.close
end
end
end
# Creates a new input method object
def initialize(file)
super
@io = IRB::MagicFile.open(file)
@external_encoding = @io.external_encoding
end
# The file name of this input method, usually given during initialization.
attr_reader :file_name
@ -137,7 +149,7 @@ module IRB
#
# See IO#eof? for more information.
def eof?
@io.eof?
@io.closed? || @io.eof?
end
# Reads the next line from this input method.
@ -150,13 +162,17 @@ module IRB
# The external encoding for standard input.
def encoding
@io.external_encoding
@external_encoding
end
# For debug message
def inspect
'FileInputMethod'
end
def close
@io.close
end
end
begin

View file

@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
"lib/irb/cmd/chws.rb",
"lib/irb/cmd/fork.rb",
"lib/irb/cmd/help.rb",
"lib/irb/cmd/info.rb",
"lib/irb/cmd/load.rb",
"lib/irb/cmd/measure.rb",
"lib/irb/cmd/nop.rb",

View file

@ -223,7 +223,10 @@ class RubyLex
throw :TERM_INPUT if @line == ''
else
@line_no += l.count("\n")
next if l == "\n"
if l == "\n"
@exp_line_no += 1
next
end
@line.concat l
if @code_block_open or @ltype or @continue or @indent > 0
next
@ -233,7 +236,7 @@ class RubyLex
@line.force_encoding(@io.encoding)
yield @line, @exp_line_no
end
break if @io.eof?
raise TerminateLineInput if @io.eof?
@line = ''
@exp_line_no = @line_no
@ -424,14 +427,30 @@ class RubyLex
indent
end
def is_method_calling?(tokens, index)
tk = tokens[index]
if tk[3].anybits?(Ripper::EXPR_CMDARG) and tk[1] == :on_ident
# The target method call to pass the block with "do".
return true
elsif tk[3].anybits?(Ripper::EXPR_ARG) and tk[1] == :on_ident
non_sp_index = tokens[0..(index - 1)].rindex{ |t| t[1] != :on_sp }
if non_sp_index
prev_tk = tokens[non_sp_index]
if prev_tk[3].anybits?(Ripper::EXPR_DOT) and prev_tk[1] == :on_period
# The target method call with receiver to pass the block with "do".
return true
end
end
end
false
end
def take_corresponding_syntax_to_kw_do(tokens, index)
syntax_of_do = nil
# Finding a syntax correnponding to "do".
index.downto(0) do |i|
tk = tokens[i]
# In "continue", the token isn't the corresponding syntax to "do".
#is_continue = process_continue(@tokens[0..(i - 1)])
# continue ではなく、直前に (:on_ignored_nl|:on_nl|:on_comment):on_sp* みたいなのがあるかどうかを調べる
non_sp_index = tokens[0..(i - 1)].rindex{ |t| t[1] != :on_sp }
first_in_fomula = false
if non_sp_index.nil?
@ -439,8 +458,7 @@ class RubyLex
elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index][1])
first_in_fomula = true
end
if tk[3].anybits?(Ripper::EXPR_CMDARG) and tk[1] == :on_ident
# The target method call to pass the block with "do".
if is_method_calling?(tokens, i)
syntax_of_do = :method_calling
break if first_in_fomula
elsif tk[1] == :on_kw && %w{while until for}.include?(tk[2])
@ -458,6 +476,34 @@ class RubyLex
syntax_of_do
end
def is_the_in_correspond_to_a_for(tokens, index)
syntax_of_in = nil
# Finding a syntax correnponding to "do".
index.downto(0) do |i|
tk = tokens[i]
# In "continue", the token isn't the corresponding syntax to "do".
non_sp_index = tokens[0..(i - 1)].rindex{ |t| t[1] != :on_sp }
first_in_fomula = false
if non_sp_index.nil?
first_in_fomula = true
elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index][1])
first_in_fomula = true
end
if tk[1] == :on_kw && tk[2] == 'for'
# A loop syntax in front of "do" found.
#
# while cond do # also "until" or "for"
# end
#
# This "do" doesn't increment indent because the loop syntax already
# incremented.
syntax_of_in = :for
end
break if first_in_fomula
end
syntax_of_in
end
def check_newline_depth_difference
depth_difference = 0
open_brace_on_line = 0
@ -513,8 +559,12 @@ class RubyLex
unless t[3].allbits?(Ripper::EXPR_LABEL)
depth_difference += 1
end
when 'else', 'elsif', 'ensure', 'when', 'in'
when 'else', 'elsif', 'ensure', 'when'
depth_difference += 1
when 'in'
unless is_the_in_correspond_to_a_for(@tokens, index)
depth_difference += 1
end
when 'end'
depth_difference -= 1
end

View file

@ -11,7 +11,7 @@
#
module IRB # :nodoc:
VERSION = "1.3.2"
VERSION = "1.3.3"
@RELEASE_VERSION = VERSION
@LAST_UPDATE_DATE = "2021-01-18"
@LAST_UPDATE_DATE = "2021-02-07"
end

View file

@ -243,6 +243,7 @@ module Reline
loop do
prev_pasting_state = Reline::IOGate.in_pasting?
read_io(config.keyseq_timeout) { |inputs|
line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
inputs.each { |c|
line_editor.input_key(c)
line_editor.rerender
@ -253,6 +254,7 @@ module Reline
end
}
if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
line_editor.set_pasting_state(false)
prev_pasting_state = false
line_editor.rerender_all
end

View file

@ -58,13 +58,17 @@ class Reline::LineEditor
reset_variables(encoding: encoding)
end
def set_pasting_state(in_pasting)
@in_pasting = in_pasting
end
def simplified_rendering?
if finished?
false
elsif @just_cursor_moving and not @rerender_all
true
else
not @rerender_all and not finished? and Reline::IOGate.in_pasting?
not @rerender_all and not finished? and @in_pasting
end
end
@ -146,6 +150,13 @@ class Reline::LineEditor
@screen_height = @screen_size.first
reset_variables(prompt, encoding: encoding)
@old_trap = Signal.trap('SIGINT') {
if @scroll_partial_screen
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
else
move_cursor_down(@highest_in_all - @line_index - 1)
end
Reline::IOGate.move_cursor_column(0)
scroll_down(1)
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
raise Interrupt
}
@ -227,6 +238,8 @@ class Reline::LineEditor
@scroll_partial_screen = nil
@prev_mode_string = nil
@drop_terminate_spaces = false
@in_pasting = false
@auto_indent_proc = nil
reset_line
end
@ -375,11 +388,29 @@ class Reline::LineEditor
@cleared = false
return
end
if @is_multiline and finished? and @scroll_partial_screen
# Re-output all code higher than the screen when finished.
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
Reline::IOGate.move_cursor_column(0)
@scroll_partial_screen = nil
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
if @previous_line_index
new_lines = whole_lines(index: @previous_line_index, line: @line)
else
new_lines = whole_lines
end
modify_lines(new_lines).each_with_index do |line, index|
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
Reline::IOGate.erase_after_cursor
end
@output.flush
return
end
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
# FIXME: end of logical line sometimes breaks
rendered = false
if @add_newline_to_end_of_buffer
rerender_added_newline
rerender_added_newline(prompt, prompt_width)
@add_newline_to_end_of_buffer = false
else
if @just_cursor_moving and not @rerender_all
@ -397,20 +428,32 @@ class Reline::LineEditor
else
end
end
line = modify_lines(whole_lines)[@line_index]
if @is_multiline
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
if finished?
# Always rerender on finish because output_modifier_proc may return a different output.
if @previous_line_index
new_lines = whole_lines(index: @previous_line_index, line: @line)
else
new_lines = whole_lines
end
line = modify_lines(new_lines)[@line_index]
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
render_partial(prompt, prompt_width, line, @first_line_started_from)
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
scroll_down(1)
Reline::IOGate.move_cursor_column(0)
Reline::IOGate.erase_after_cursor
elsif not rendered
render_partial(prompt, prompt_width, line, @first_line_started_from)
unless @in_pasting
line = modify_lines(whole_lines)[@line_index]
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
render_partial(prompt, prompt_width, line, @first_line_started_from)
end
end
@buffer_of_lines[@line_index] = @line
@rest_height = 0 if @scroll_partial_screen
else
line = modify_lines(whole_lines)[@line_index]
render_partial(prompt, prompt_width, line, 0)
if finished?
scroll_down(1)
@ -453,13 +496,13 @@ class Reline::LineEditor
end
end
private def rerender_added_newline
private def rerender_added_newline(prompt, prompt_width)
scroll_down(1)
new_lines = whole_lines(index: @previous_line_index, line: @line)
prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
@buffer_of_lines[@previous_line_index] = @line
@line = @buffer_of_lines[@line_index]
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
unless @in_pasting
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
end
@cursor = @cursor_max = calculate_width(@line)
@byte_pointer = @line.bytesize
@highest_in_all += @highest_in_this
@ -568,7 +611,13 @@ class Reline::LineEditor
new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
end
new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
if back > old_highest_in_all
calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
if @scroll_partial_screen
move_cursor_up(@first_line_started_from + @started_from)
scroll_down(@screen_height - 1)
move_cursor_up(@screen_height)
Reline::IOGate.move_cursor_column(0)
elsif back > old_highest_in_all
scroll_down(back - 1)
move_cursor_up(back - 1)
elsif back < old_highest_in_all
@ -580,7 +629,6 @@ class Reline::LineEditor
end
move_cursor_up(old_highest_in_all - 1)
end
calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
if @prompt_proc
prompt = prompt_list[@line_index]
@ -666,8 +714,8 @@ class Reline::LineEditor
@highest_in_this = height
end
move_cursor_up(@started_from)
cursor_up_from_last_line = height - 1 - @started_from
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
cursor_up_from_last_line = height - 1 - @started_from
end
if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
@output.write "\e[0m" # clear character decorations
@ -1082,7 +1130,7 @@ class Reline::LineEditor
unless completion_occurs
@completion_state = CompletionState::NORMAL
end
if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
if not @in_pasting and @just_cursor_moving.nil?
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
@just_cursor_moving = true
elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
@ -1286,7 +1334,11 @@ class Reline::LineEditor
if @buffer_of_lines.size == 1 and @line.nil?
nil
else
whole_lines.join("\n")
if @previous_line_index
whole_lines(index: @previous_line_index, line: @line).join("\n")
else
whole_lines.join("\n")
end
end
end
@ -1332,14 +1384,14 @@ class Reline::LineEditor
cursor_line = @line.byteslice(0, @byte_pointer)
insert_new_line(cursor_line, next_line)
@cursor = 0
@check_new_auto_indent = true unless Reline::IOGate.in_pasting?
@check_new_auto_indent = true unless @in_pasting
end
end
private def ed_unassigned(key) end # do nothing
private def process_insert(force: false)
return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
bytesize = @continuous_insertion_buffer.bytesize
if @cursor == @cursor_max
@ -1374,7 +1426,7 @@ class Reline::LineEditor
str = key.chr
bytesize = 1
end
if Reline::IOGate.in_pasting?
if @in_pasting
@continuous_insertion_buffer << str
return
elsif not @continuous_insertion_buffer.empty?
@ -2424,11 +2476,23 @@ class Reline::LineEditor
private def vi_histedit(key)
path = Tempfile.open { |fp|
fp.write @line
if @is_multiline
fp.write whole_lines.join("\n")
else
fp.write @line
end
fp.path
}
system("#{ENV['EDITOR']} #{path}")
@line = File.read(path)
if @is_multiline
@buffer_of_lines = File.read(path).split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = 0
@line = @buffer_of_lines[@line_index]
@rerender_all = true
else
@line = File.read(path)
end
finish
end

View file

@ -1,3 +1,3 @@
module Reline
VERSION = '0.2.2'
VERSION = '0.2.3'
end

View file

@ -275,5 +275,98 @@ module TestIRB
assert_empty err
assert_match(/\A=> 3\nCUSTOM is added\.\n=> nil\ncustom processing time: .+\n=> 3\n=> nil\n=> 3\n/, out)
end
def test_measure_with_proc
IRB.init_config(nil)
IRB.conf[:PROMPT] = {
DEFAULT: {
PROMPT_I: '> ',
PROMPT_S: '> ',
PROMPT_C: '> ',
PROMPT_N: '> '
}
}
IRB.conf[:VERBOSE] = false
IRB.conf[:PROMPT_MODE] = :DEFAULT
IRB.conf[:MEASURE] = false
input = TestInputMethod.new([
"3\n",
"measure { |context, code, line_no, &block|\n",
" result = block.()\n",
" puts 'aaa' if IRB.conf[:MEASURE]\n",
" result\n",
"}\n",
"3\n",
"measure { |context, code, line_no, &block|\n",
" result = block.()\n",
" puts 'bbb' if IRB.conf[:MEASURE]\n",
" result\n",
"}\n",
"3\n",
"measure :off\n",
"3\n",
])
c = Class.new(Object)
irb = IRB::Irb.new(IRB::WorkSpace.new(c.new), input)
irb.context.return_format = "=> %s\n"
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(/\A=> 3\nBLOCK is added\.\n=> nil\naaa\n=> 3\nBLOCK is added.\naaa\n=> nil\nbbb\n=> 3\n=> nil\n=> 3\n/, out)
assert_empty(c.class_variables)
end
def test_irb_source
IRB.init_config(nil)
File.write("#{@tmpdir}/a.rb", "a = 'hi'\n")
input = TestInputMethod.new([
"a = 'bug17564'\n",
"a\n",
"irb_source '#{@tmpdir}/a.rb'\n",
"a\n",
])
IRB.conf[:VERBOSE] = false
IRB.conf[:PROMPT_MODE] = :SIMPLE
irb = IRB::Irb.new(IRB::WorkSpace.new(self), input)
IRB.conf[:MAIN_CONTEXT] = irb.context
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_pattern_list([
/=> "bug17564"\n/,
/=> "bug17564"\n/,
/ => "hi"\n/,
/ => nil\n/,
/=> "hi"\n/,
], out)
end
def test_irb_load
IRB.init_config(nil)
File.write("#{@tmpdir}/a.rb", "a = 'hi'\n")
input = TestInputMethod.new([
"a = 'bug17564'\n",
"a\n",
"irb_load '#{@tmpdir}/a.rb'\n",
"a\n",
])
IRB.conf[:VERBOSE] = false
IRB.conf[:PROMPT_MODE] = :SIMPLE
irb = IRB::Irb.new(IRB::WorkSpace.new(self), input)
IRB.conf[:MAIN_CONTEXT] = irb.context
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_pattern_list([
/=> "bug17564"\n/,
/=> "bug17564"\n/,
/ => "hi"\n/,
/ => nil\n/,
/=> "bug17564"\n/,
], out)
end
end
end

View file

@ -1,7 +1,6 @@
# frozen_string_literal: false
require 'test/unit'
require 'irb/color'
require 'irb/color_printer'
require 'rubygems'
require 'stringio'
@ -153,23 +152,6 @@ module TestIRB
end
end
IRBTestColorPrinter = Struct.new(:a)
def test_color_printer
unless ripper_lexer_scan_supported?
skip 'Ripper::Lexer#scan is supported in Ruby 2.7+'
end
{
1 => "#{BLUE}#{BOLD}1#{CLEAR}\n",
IRBTestColorPrinter.new('test') => "#{GREEN}#<struct TestIRB::TestColor::IRBTestColorPrinter#{CLEAR} a#{GREEN}=#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{RED}test#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}\n",
Ripper::Lexer.new('1').scan => "[#{GREEN}#<Ripper::Lexer::Elem:#{CLEAR} on_int@1:0 END token: #{RED}#{BOLD}\"#{CLEAR}#{RED}1#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}]\n",
Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]\n",
}.each do |object, result|
actual = with_term { IRB::ColorPrinter.pp(object, '') }
assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')")
end
end
def test_inspect_colorable
{
1 => true,
@ -202,10 +184,6 @@ module TestIRB
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0')
end
def ripper_lexer_scan_supported?
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0')
end
def with_term
stdout = $stdout
io = StringIO.new

View file

@ -0,0 +1,67 @@
# frozen_string_literal: false
require 'test/unit'
require 'irb/color_printer'
require 'rubygems'
require 'stringio'
module TestIRB
class TestColorPrinter < Test::Unit::TestCase
CLEAR = "\e[0m"
BOLD = "\e[1m"
RED = "\e[31m"
GREEN = "\e[32m"
BLUE = "\e[34m"
CYAN = "\e[36m"
def setup
@get_screen_size = Reline.method(:get_screen_size)
Reline.instance_eval { undef :get_screen_size }
def Reline.get_screen_size
[36, 80]
end
end
def teardown
Reline.instance_eval { undef :get_screen_size }
Reline.define_singleton_method(:get_screen_size, @get_screen_size)
end
IRBTestColorPrinter = Struct.new(:a)
def test_color_printer
unless ripper_lexer_scan_supported?
skip 'Ripper::Lexer#scan is supported in Ruby 2.7+'
end
{
1 => "#{BLUE}#{BOLD}1#{CLEAR}\n",
IRBTestColorPrinter.new('test') => "#{GREEN}#<struct TestIRB::TestColorPrinter::IRBTestColorPrinter#{CLEAR} a#{GREEN}=#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{RED}test#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}\n",
Ripper::Lexer.new('1').scan => "[#{GREEN}#<Ripper::Lexer::Elem:#{CLEAR} on_int@1:0 END token: #{RED}#{BOLD}\"#{CLEAR}#{RED}1#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}]\n",
Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]\n",
}.each do |object, result|
actual = with_term { IRB::ColorPrinter.pp(object, '') }
assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')")
end
end
private
def ripper_lexer_scan_supported?
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0')
end
def with_term
stdout = $stdout
io = StringIO.new
def io.tty?; true; end
$stdout = io
env = ENV.to_h.dup
ENV['TERM'] = 'xterm-256color'
yield
ensure
$stdout = stdout
ENV.replace(env) if env
end
end
end

View file

@ -42,6 +42,17 @@ module TestIRB
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(Object.new)
@context = IRB::Context.new(nil, workspace, TestInputMethod.new)
@get_screen_size = Reline.method(:get_screen_size)
Reline.instance_eval { undef :get_screen_size }
def Reline.get_screen_size
[36, 80]
end
end
def teardown
Reline.instance_eval { undef :get_screen_size }
Reline.define_singleton_method(:get_screen_size, @get_screen_size)
end
def test_last_value
@ -447,7 +458,7 @@ module TestIRB
irb.eval_input
end
assert_empty err
if '2.5.0' <= RUBY_VERSION && RUBY_VERSION < '3.0.0'
if '2.5.0' <= RUBY_VERSION && RUBY_VERSION < '3.0.0' && STDOUT.tty?
expected = [
:*, /Traceback \(most recent call last\):\n/,
:*, /\t 2: from \(irb\):1:in `<main>'\n/,
@ -477,7 +488,7 @@ module TestIRB
irb.eval_input
end
assert_empty err
if '2.5.0' <= RUBY_VERSION && RUBY_VERSION < '3.0.0'
if '2.5.0' <= RUBY_VERSION && RUBY_VERSION < '3.0.0' && STDOUT.tty?
expected = [
:*, /Traceback \(most recent call last\):\n/,
:*, /\t 2: from \(irb\):1:in `<main>'\n/,
@ -513,7 +524,7 @@ module TestIRB
irb.eval_input
end
assert_empty err
if '2.5.0' <= RUBY_VERSION && RUBY_VERSION < '3.0.0'
if '2.5.0' <= RUBY_VERSION && RUBY_VERSION < '3.0.0' && STDOUT.tty?
expected = [
:*, /Traceback \(most recent call last\):\n/,
:*, /\t... 5 levels...\n/,
@ -561,5 +572,26 @@ module TestIRB
ensure
$VERBOSE = verbose
end
def test_lineno
input = TestInputMethod.new([
"\n",
"__LINE__\n",
"__LINE__\n",
"\n",
"\n",
"__LINE__\n",
])
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_pattern_list([
:*, /\b2\n/,
:*, /\b3\n/,
:*, /\b6\n/,
], out)
end
end
end

View file

@ -333,6 +333,102 @@ module TestIRB
end
end
def test_corresponding_syntax_to_keyword_for
input_with_correct_indents = [
Row.new(%q(for i in [1]), nil, 2, 1),
Row.new(%q( puts i), nil, 2, 1),
Row.new(%q(end), 0, 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
assert_nesting_level(lines, row.nesting_level)
end
end
def test_corresponding_syntax_to_keyword_for_with_do
input_with_correct_indents = [
Row.new(%q(for i in [1] do), nil, 2, 1),
Row.new(%q( puts i), nil, 2, 1),
Row.new(%q(end), 0, 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
assert_nesting_level(lines, row.nesting_level)
end
end
def test_bracket_corresponding_to_times
input_with_correct_indents = [
Row.new(%q(3.times { |i|), nil, 2, 1),
Row.new(%q( puts i), nil, 2, 1),
Row.new(%q(}), 0, 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
assert_nesting_level(lines, row.nesting_level)
end
end
def test_do_corresponding_to_times
input_with_correct_indents = [
Row.new(%q(3.times do |i|), nil, 2, 1),
#Row.new(%q( puts i), nil, 2, 1),
#Row.new(%q(end), 0, 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
assert_nesting_level(lines, row.nesting_level)
end
end
def test_bracket_corresponding_to_loop
input_with_correct_indents = [
Row.new(%q(loop {), nil, 2, 1),
Row.new(%q( 3), nil, 2, 1),
Row.new(%q(}), 0, 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
assert_nesting_level(lines, row.nesting_level)
end
end
def test_do_corresponding_to_loop
input_with_correct_indents = [
Row.new(%q(loop do), nil, 2, 1),
Row.new(%q( 3), nil, 2, 1),
Row.new(%q(end), 0, 0, 0),
]
lines = []
input_with_correct_indents.each do |row|
lines << row.content
assert_indenting(lines, row.current_line_spaces, false)
assert_indenting(lines, row.new_line_spaces, true)
assert_nesting_level(lines, row.nesting_level)
end
end
def test_heredoc_with_indent
input_with_correct_indents = [
Row.new(%q(<<~Q), nil, 0, 0),

View file

@ -678,6 +678,34 @@ begin
EOC
end
def test_autowrap_in_the_middle_of_a_line
start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl}, startup_message: 'Multiline REPL.')
write("def abcdefg; end\C-b\C-b\C-b\C-b\C-b")
%w{h i}.each do |c|
write(c)
end
close
assert_screen(<<~EOC)
Multiline REPL.
prompt> def abcdefgh
i; end
EOC
end
def test_terminate_in_the_middle_of_lines
start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl}, startup_message: 'Multiline REPL.')
write("def hoge\n 1\n 2\n 3\n 4\nend\n")
write("\C-p\C-p\C-p\C-e\n")
close
assert_screen(<<~EOC)
prompt> 3
prompt> 4
prompt> end
=> :hoge
prompt>
EOC
end
private def write_inputrc(content)
File.open(@inputrc_file, 'w') do |f|
f.write content