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 4th (#4349)

* [ruby/irb] Update help message for next context-mode of 4

While here, fixing tab/space issues in help message, and sync
rdoc for IRB class to match the help message.

https://github.com/ruby/irb/commit/ef8e3901cc

* [ruby/irb] Do not continue line if last expression is an endless range

Fixes [Bug #14824]

https://github.com/ruby/irb/commit/63414f8465

* [ruby/irb] Add a test for not continuing when endless range at eol

https://github.com/ruby/irb/commit/1020ac9c65

* [ruby/irb] Make save-history extension safe for concurrent use

This makes the save-history extension check for modifications to
the history file before saving it.  If the history file was modified
after the history was loaded and before it was saved, append only
the new history lines to the history file.

This can result in more lines in the history file than SAVE_HISTORY
allows.  However, that will be fixed the next time irb is run and
the history is saved.

Fixes [Bug #13654]

https://github.com/ruby/irb/commit/041ef53845

* Fix errors when XDG_CONFIG_HOME points to non-writable directory

`$HOME/.config` is not writable on CI
because I think tests should not corrupt user's data.

And GitHub Actions CI sets `XDG_CONFIG_HOME`
since `Version: 20210309.1`.

https://github.com/ruby/actions/runs/2130811016?check_suite_focus=true#step:16:301
```
Errno::EACCES: Permission denied @ dir_s_mkdir - /home/runner/.config/irb
```

* Try to fix errors in TestIRB::TestHistory too

https://github.com/ruby/actions/runs/2137935523?check_suite_focus=true#step:9:562
```
  1) Error:
TestIRB::TestHistory#test_history_concurrent_use:
Errno::EACCES: Permission denied @ dir_s_mkdir - /home/runner/.config/irb
    /home/runner/work/actions/actions/ruby/lib/fileutils.rb:253:in `mkdir'
    /home/runner/work/actions/actions/ruby/lib/fileutils.rb:253:in `fu_mkdir'
    /home/runner/work/actions/actions/ruby/lib/fileutils.rb:231:in `block (2 levels) in mkdir_p'
    /home/runner/work/actions/actions/ruby/lib/fileutils.rb:229:in `reverse_each'
    /home/runner/work/actions/actions/ruby/lib/fileutils.rb:229:in `block in mkdir_p'
    /home/runner/work/actions/actions/ruby/lib/fileutils.rb:211:in `each'
    /home/runner/work/actions/actions/ruby/lib/fileutils.rb:211:in `mkdir_p'
    /home/runner/work/actions/actions/ruby/lib/irb/init.rb:355:in `rc_file_generators'
    /home/runner/work/actions/actions/ruby/lib/irb/init.rb:330:in `rc_file'
    /home/runner/work/actions/actions/ruby/test/irb/test_history.rb:170:in `block in assert_history'
    /home/runner/work/actions/actions/ruby/lib/tmpdir.rb:96:in `mktmpdir'
    /home/runner/work/actions/actions/ruby/test/irb/test_history.rb:168:in `assert_history'
    /home/runner/work/actions/actions/ruby/test/irb/test_history.rb:133:in `test_history_concurrent_use'
```

* [ruby/irb] Define "measure" command without forced override

https://github.com/ruby/irb/commit/9587ba13b5

* [ruby/irb] Add all lib files automatically

https://github.com/ruby/irb/commit/ecc82336b7

* [ruby/irb] Don't call Ruby 2.4+'s String#pretty_print

https://github.com/ruby/irb/commit/89bcf107be

* [ruby/irb] Implement ls command

https://github.com/ruby/irb/commit/19b6c20604

* [ruby/irb] Add whereami command

https://github.com/ruby/irb/commit/bc822e4aac

* [ruby/irb] Fix column overflow on ls output

https://github.com/ruby/irb/commit/6115754623

* [ruby/irb] Fix step's argument

cols.size was calling Integer#size, which returns 8.

Fixing a bug of https://github.com/ruby/irb/pull/209

https://github.com/ruby/irb/commit/c93ae4be71

* [ruby/irb] Deal with different screen sizes

e.g. http://rubyci.s3.amazonaws.com/centos8/ruby-master/log/20210321T063003Z.fail.html.gz

https://github.com/ruby/irb/commit/ddb3472ba2

* [ruby/irb] Have some right padding

instead of filling out an entire line

https://github.com/ruby/irb/commit/6ac8f45f5f

* Suppress verbose messages

Get rid of warnings in the parallel test.

```
unknown command: "Switch to inspect mode."
```

* [ruby/irb] Change ripper_lex_without_warning to a class method

https://github.com/ruby/irb/commit/d9f8abc17e

* [ruby/irb] Complete require and require_relative

https://github.com/ruby/irb/commit/1c61178b4c

* [ruby/reline] Add Reline.ungetc to control buffer

https://github.com/ruby/reline/commit/43ac03c624

* [ruby/reline] Reline.delete_text removes the current line in multiline

https://github.com/ruby/reline/commit/da90c094a1

* [ruby/reline] Support preposing and postposing for Reline.completion_proc

https://github.com/ruby/reline/commit/1f469de90c

* [ruby/reline] Suppress crashing when completer_{quote,word_break}_characters is empty

https://github.com/ruby/reline/commit/c6f1164942

* [ruby/irb] fix completion test when out-of-place build

* [ruby/irb] Cache completion files to require

https://github.com/ruby/irb/commit/612ebcb311

* [ruby/irb] Always add input method when calling Irb.new in tests

When passes input method as nil to Context.new through Irb.new,
ReidlineInputMethod.new is executed and the global internal state of Reline is
rewritten, therefore other tests are failed in the Ruby repository. This
commit changes to use TestInputMethod.

https://github.com/ruby/irb/commit/010dce9210

* [ruby/irb] Prevent the completion from crashing if rdoc is missing

There are cases where ruby is installed without rdoc and e.g.
lib/irb/cmd/help.rb also handles the LoadError

Here is how to replicate the issue:

```
$ docker run -it alpine:3.13.3 sh

/ # apk add ruby ruby-irb ruby-io-console

/ # irb

irb(main):001:0> Class[TAB][TAB]
```

And you end up with something like:

```
irb(main):001:0> ClassTraceback (most recent call last):
        34: from /usr/bin/irb:23:in `<main>'
        33: from /usr/bin/irb:23:in `load'
        32: from /usr/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        31: from /usr/lib/ruby/2.7.0/irb.rb:400:in `start'
        30: from /usr/lib/ruby/2.7.0/irb.rb:471:in `run'
        29: from /usr/lib/ruby/2.7.0/irb.rb:471:in `catch'
        28: from /usr/lib/ruby/2.7.0/irb.rb:472:in `block in run'
        27: from /usr/lib/ruby/2.7.0/irb.rb:537:in `eval_input'
        26: from /usr/lib/ruby/2.7.0/irb/ruby-lex.rb:150:in `each_top_level_statement'
        25: from /usr/lib/ruby/2.7.0/irb/ruby-lex.rb:150:in `catch'
        24: from /usr/lib/ruby/2.7.0/irb/ruby-lex.rb:151:in `block in each_top_level_statement'
        23: from /usr/lib/ruby/2.7.0/irb/ruby-lex.rb:151:in `loop'
        22: from /usr/lib/ruby/2.7.0/irb/ruby-lex.rb:154:in `block (2 levels) in each_top_level_statement'
        21: from /usr/lib/ruby/2.7.0/irb/ruby-lex.rb:182:in `lex'
        20: from /usr/lib/ruby/2.7.0/irb.rb:518:in `block in eval_input'
        19: from /usr/lib/ruby/2.7.0/irb.rb:704:in `signal_status'
        18: from /usr/lib/ruby/2.7.0/irb.rb:519:in `block (2 levels) in eval_input'
        17: from /usr/lib/ruby/2.7.0/irb/input-method.rb:294:in `gets'
        16: from /usr/lib/ruby/2.7.0/forwardable.rb:235:in `readmultiline'
        15: from /usr/lib/ruby/2.7.0/forwardable.rb:235:in `readmultiline'
        14: from /usr/lib/ruby/2.7.0/reline.rb:175:in `readmultiline'
        13: from /usr/lib/ruby/2.7.0/reline.rb:238:in `inner_readline'
        12: from /usr/lib/ruby/2.7.0/reline.rb:238:in `loop'
        11: from /usr/lib/ruby/2.7.0/reline.rb:239:in `block in inner_readline'
        10: from /usr/lib/ruby/2.7.0/reline.rb:270:in `read_io'
         9: from /usr/lib/ruby/2.7.0/reline.rb:270:in `loop'
         8: from /usr/lib/ruby/2.7.0/reline.rb:311:in `block in read_io'
         7: from /usr/lib/ruby/2.7.0/reline.rb:240:in `block (2 levels) in inner_readline'
         6: from /usr/lib/ruby/2.7.0/reline.rb:240:in `each'
         5: from /usr/lib/ruby/2.7.0/reline.rb:241:in `block (3 levels) in inner_readline'
         4: from /usr/lib/ruby/2.7.0/reline/line_editor.rb:820:in `input_key'
         3: from /usr/lib/ruby/2.7.0/reline/line_editor.rb:608:in `complete'
         2: from /usr/lib/ruby/2.7.0/irb/completion.rb:269:in `block in <module:InputCompletor>'
         1: from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
/usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require': cannot load such file -- rdoc (LoadError)
```

https://github.com/ruby/irb/commit/a2d299c2ac

* [ruby/irb] Suppress verbose messages in the parallel test

`:VERBOSE` flag needs to be set prior to `IRB::Irb.new`.

https://github.com/ruby/irb/commit/78604682d9

* [ruby/irb] SIGINT should raise Interrupt after IRB session

https://github.com/ruby/irb/commit/5832cfe75b

* [ruby/irb] Colorize `__END__` as keyword

https://github.com/ruby/irb/commit/9b84018311

* [ruby/irb] Add show_source command

https://github.com/ruby/irb/commit/108cb04352

* [ruby/reline] Reset @rest_height when clear screen

https://github.com/ruby/reline/commit/3a7019b0d5

* [ruby/irb] process multi-line pastes as a single entity

this allows pasting leading-dot chained methods correctly:

```ruby
class A
  def a; self; end
  def b; true; end
end

a = A.new

a
 .a
 .b
```

will properly return `true` instead of erroring on the `.a` line:

```
irb(main):001:1*     class A
irb(main):002:1*       def a; self; end
irb(main):003:0>     end
irb(main):004:0*
irb(main):005:0>     a = A.new
irb(main):006:0*
irb(main):007:0>     a
irb(main):008:0>      .a
irb(main):009:0>      .a
=> #<A:0x00007f984211fbe8>
```

https://github.com/ruby/irb/commit/45aeb52575

* [ruby/irb] Add yamatanooroti test example

https://github.com/ruby/irb/commit/279155fcee

* [ruby/irb] Add test for multiline paste

https://github.com/ruby/irb/commit/e93c9cb54d

* [ruby/irb] Evaluate each toplevel statement

https://github.com/ruby/irb/commit/bc1b1d8bc3

* [ruby/irb] Version 1.3.5

https://github.com/ruby/irb/commit/22e2ddf715

* [ruby/reline] Version 0.2.5

https://github.com/ruby/reline/commit/22ce5651e5

Co-authored-by: Jeremy Evans <code@jeremyevans.net>
Co-authored-by: Kazuhiro NISHIYAMA <zn@mbf.nifty.com>
Co-authored-by: Takashi Kokubun <takashikkbn@gmail.com>
Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: Aleksandar Ivanov <aivanov92@gmail.com>
Co-authored-by: Koichi Sasada <ko1@atdot.net>
Co-authored-by: Cody Cutrer <cody@instructure.com>
This commit is contained in:
aycabta 2021-04-03 18:26:46 +09:00 committed by GitHub
parent 6abb8ee711
commit cafa7904e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1009 additions and 228 deletions

View file

@ -60,7 +60,11 @@ require_relative "irb/easter-egg"
# -E enc Same as `ruby -E`
# -w Same as `ruby -w`
# -W[level=2] Same as `ruby -W`
# --inspect Use `inspect' for output (default except for bc mode)
# --context-mode n Set n[0-4] to method to create Binding Object,
# when new workspace was created
# --echo Show result(default)
# --noecho Don't show result
# --inspect Use `inspect' for output
# --noinspect Don't use inspect for output
# --multiline Use multiline editor module
# --nomultiline Don't use multiline editor module
@ -68,19 +72,24 @@ require_relative "irb/easter-egg"
# --nosingleline Don't use singleline editor module
# --colorize Use colorization
# --nocolorize Don't use colorization
# --prompt prompt-mode
# --prompt-mode prompt-mode
# --prompt prompt-mode/--prompt-mode prompt-mode
# Switch prompt mode. Pre-defined prompt modes are
# `default', `simple', `xmp' and `inf-ruby'
# --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
# Suppresses --multiline and --singleline.
# --simple-prompt Simple prompt mode
# --sample-book-mode/--simple-prompt
# Simple prompt mode
# --noprompt No prompt mode
# --single-irb Share self with sub-irb.
# --tracer Display trace for each execution of commands.
# --back-trace-limit n
# Display backtrace top n and tail n. The default
# value is 16.
# -v, --version Print the version of irb
# --verbose Show details
# --noverbose Don't show details
# -v, --version Print the version of irb
# -h, --help Print help
# -- Separate options of irb from the list of command-line args
#
# == Configuration
#
@ -463,7 +472,7 @@ module IRB
conf[:IRB_RC].call(context) if conf[:IRB_RC]
conf[:MAIN_CONTEXT] = context
trap("SIGINT") do
prev_trap = trap("SIGINT") do
signal_handle
end
@ -472,6 +481,7 @@ module IRB
eval_input
end
ensure
trap("SIGINT", prev_trap)
conf[:AT_EXIT].each{|hook| hook.call}
end
end

83
lib/irb/cmd/ls.rb Normal file
View file

@ -0,0 +1,83 @@
# frozen_string_literal: true
require "reline"
require_relative "nop"
require_relative "../color"
# :stopdoc:
module IRB
module ExtendCommand
class Ls < Nop
def execute(*arg, grep: nil)
o = Output.new(grep: grep)
obj = arg.empty? ? irb_context.workspace.main : arg.first
locals = arg.empty? ? irb_context.workspace.binding.local_variables : []
klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
o.dump("constants", obj.constants) if obj.respond_to?(:constants)
o.dump("#{klass}.methods", obj.singleton_methods(false))
o.dump("#{klass}#methods", klass.public_instance_methods(false))
o.dump("instance variables", obj.instance_variables)
o.dump("class variables", klass.class_variables)
o.dump("locals", locals)
end
class Output
MARGIN = " "
def initialize(grep: nil)
@grep = grep
@line_width = screen_width - MARGIN.length # right padding
end
def dump(name, strs)
strs = strs.grep(@grep) if @grep
strs = strs.sort
return if strs.empty?
# Attempt a single line
print "#{Color.colorize(name, [:BOLD, :BLUE])}: "
if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length)
puts strs.join(MARGIN)
return
end
puts
# Dump with the largest # of columns that fits on a line
cols = strs.size
until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1
cols -= 1
end
widths = col_widths(strs, cols: cols)
strs.each_slice(cols) do |ss|
puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join
end
end
private
def fits_on_line?(strs, cols:, offset: 0)
width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1)
width <= @line_width - offset
end
def col_widths(strs, cols:)
cols.times.map do |col|
(col...strs.size).step(cols).map do |i|
strs[i].length
end.max
end
end
def screen_width
Reline.get_screen_size.last
rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN>
80
end
end
private_constant :Output
end
end
end
# :startdoc:

View file

@ -14,10 +14,16 @@ module IRB
module ExtendCommand
class Nop
def self.execute(conf, *opts, &block)
command = new(conf)
command.execute(*opts, &block)
if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0"
def self.execute(conf, *opts, **kwargs, &block)
command = new(conf)
command.execute(*opts, **kwargs, &block)
end
else
def self.execute(conf, *opts, &block)
command = new(conf)
command.execute(*opts, &block)
end
end
def initialize(conf)

View file

@ -0,0 +1,86 @@
# frozen_string_literal: true
require_relative "nop"
require_relative "../color"
require_relative "../ruby-lex"
# :stopdoc:
module IRB
module ExtendCommand
class ShowSource < Nop
def execute(str = nil)
unless str.is_a?(String)
puts "Error: Expected a string but got #{str.inspect}"
return
end
source = find_source(str)
if source && File.exist?(source.file)
show_source(source)
else
puts "Error: Couldn't locate a definition for #{str}"
end
nil
end
private
# @param [IRB::ExtendCommand::ShowSource::Source] source
def show_source(source)
puts
puts "#{bold("From")}: #{source.file}:#{source.first_line}"
puts
code = IRB::Color.colorize_code(File.read(source.file))
puts code.lines[(source.first_line - 1)...source.last_line].join
puts
end
def find_source(str)
case str
when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
eval(str, irb_context.workspace.binding) # trigger autoload
base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
method = Regexp.last_match[:method]
if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
file, line = owner.instance_method(method).source_location
end
when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
method = Regexp.last_match[:method]
file, line = receiver.method(method).source_location if receiver.respond_to?(method)
end
if file && line
Source.new(file: file, first_line: line, last_line: find_end(file, line))
end
end
def find_end(file, first_line)
return first_line unless File.exist?(file)
lex = RubyLex.new
code = +""
File.read(file).lines[(first_line - 1)..-1].each_with_index do |line, i|
_ltype, _indent, continue, code_block_open = lex.check_state(code << line)
if !continue && !code_block_open
return first_line + i
end
end
first_line
end
def bold(str)
Color.colorize(str, [:BOLD])
end
Source = Struct.new(
:file, # @param [String] - file name
:first_line, # @param [String] - first line
:last_line, # @param [String] - last line
keyword_init: true,
)
private_constant :Source
end
end
end
# :startdoc:

20
lib/irb/cmd/whereami.rb Normal file
View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative "nop"
# :stopdoc:
module IRB
module ExtendCommand
class Whereami < Nop
def execute(*)
code = irb_context.workspace.code_around_binding
if code
puts code
else
puts "The current context doesn't have code."
end
end
end
end
end
# :startdoc:

View file

@ -64,6 +64,7 @@ module IRB # :nodoc:
on_alias_error: [[RED, REVERSE], ALL],
on_class_name_error:[[RED, REVERSE], ALL],
on_param_error: [[RED, REVERSE], ALL],
on___end__: [[GREEN], ALL],
}
rescue NameError
# Give up highlighting Ripper-incompatible older Ruby
@ -120,6 +121,7 @@ module IRB # :nodoc:
symbol_state = SymbolState.new
colored = +''
length = 0
end_seen = false
scan(code, allow_last_error: !complete) do |token, str, expr|
# IRB::ColorPrinter skips colorizing fragments with any invalid token
@ -138,10 +140,11 @@ module IRB # :nodoc:
end
end
length += str.bytesize
end_seen = true if token == :on___end__
end
# give up colorizing incomplete Ripper tokens
if length != code.bytesize
unless end_seen or length == code.bytesize
return Reline::Unicode.escape_for_print(code)
end

View file

@ -21,6 +21,15 @@ module IRB
end
end
def pp(obj)
if obj.is_a?(String)
# Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n"
text(obj.inspect)
else
super
end
end
def text(str, width = nil)
unless str.is_a?(String)
str = str.inspect

View file

@ -7,7 +7,7 @@
# From Original Idea of shugo@ruby-lang.org
#
autoload :RDoc, "rdoc"
require_relative 'ruby-lex'
module IRB
module InputCompletor # :nodoc:
@ -38,8 +38,69 @@ module IRB
BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{("
CompletionProc = proc { |input|
retrieve_completion_data(input).compact.map{ |i| i.encode(Encoding.default_external) }
def self.retrieve_files_to_require_from_load_path
@@files_from_load_path ||= $LOAD_PATH.flat_map { |path|
begin
Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path)
rescue Errno::ENOENT
[]
end
}.uniq.map { |path|
path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '')
}
end
def self.retrieve_files_to_require_relative_from_current_dir
@@files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path|
path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '')
}
end
CompletionRequireProc = lambda { |target, preposing = nil, postposing = nil|
if target =~ /\A(['"])([^'"]+)\Z/
quote = $1
actual_target = $2
else
return nil # It's not String literal
end
tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, ''))
tok = nil
tokens.reverse_each do |t|
unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event)
tok = t
break
end
end
result = []
if tok && tok.event == :on_ident && tok.state == Ripper::EXPR_CMDARG
case tok.tok
when 'require'
result = retrieve_files_to_require_from_load_path.select { |path|
path.start_with?(actual_target)
}.map { |path|
quote + path
}
when 'require_relative'
result = retrieve_files_to_require_relative_from_current_dir.select { |path|
path.start_with?(actual_target)
}.map { |path|
quote + path
}
end
end
result
}
CompletionProc = lambda { |target, preposing = nil, postposing = nil|
if preposing && postposing
result = CompletionRequireProc.(target, preposing, postposing)
unless result
result = retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) }
end
result
else
retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) }
end
}
def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding, doc_namespace: false)
@ -266,13 +327,22 @@ module IRB
end
PerfectMatchedProc = ->(matched, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) {
begin
require 'rdoc'
rescue LoadError
return
end
RDocRIDriver ||= RDoc::RI::Driver.new
if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
IRB.__send__(:easter_egg)
return
end
namespace = retrieve_completion_data(matched, bind: bind, doc_namespace: true)
return unless namespace
if namespace.is_a?(Array)
out = RDoc::Markup::Document.new
namespace.each do |m|

View file

@ -81,6 +81,8 @@ module IRB
end
}
end
@loaded_history_lines = history.size
@loaded_history_mtime = File.mtime(history_file)
end
end
@ -105,12 +107,20 @@ module IRB
raise
end
open(history_file, "w:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f|
if File.exist?(history_file) && @loaded_history_mtime &&
File.mtime(history_file) != @loaded_history_mtime
history = history[@loaded_history_lines..-1]
append_history = true
end
open(history_file, "#{append_history ? 'a' : 'w'}:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f|
hist = history.map{ |l| l.split("\n").join("\\\n") }
begin
hist = hist.last(num) if hist.size > num and num > 0
rescue RangeError # bignum too big to convert into `long'
# Do nothing because the bignum should be treated as inifinity
unless append_history
begin
hist = hist.last(num) if hist.size > num and num > 0
rescue RangeError # bignum too big to convert into `long'
# Do nothing because the bignum should be treated as inifinity
end
end
f.puts(hist)
end

View file

@ -126,7 +126,23 @@ module IRB # :nodoc:
],
[
:measure, :Measure, "irb/cmd/measure"
:irb_ls, :Ls, "irb/cmd/ls",
[:ls, NO_OVERRIDE],
],
[
:irb_measure, :Measure, "irb/cmd/measure",
[:measure, NO_OVERRIDE],
],
[
:irb_show_source, :ShowSource, "irb/cmd/show_source",
[:show_source, NO_OVERRIDE],
],
[
:irb_whereami, :Whereami, "irb/cmd/whereami",
[:whereami, NO_OVERRIDE],
],
]
@ -168,12 +184,13 @@ module IRB # :nodoc:
end
if load_file
kwargs = ", **kwargs" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0"
line = __LINE__; eval %[
def #{cmd_name}(*opts, &b)
def #{cmd_name}(*opts#{kwargs}, &b)
require "#{load_file}"
arity = ExtendCommand::#{cmd_class}.instance_method(:execute).arity
args = (1..(arity < 0 ? ~arity : arity)).map {|i| "arg" + i.to_s }
args << "*opts" if arity < 0
args << "*opts#{kwargs}" if arity < 0
args << "&block"
args = args.join(", ")
line = __LINE__; eval %[
@ -184,7 +201,7 @@ module IRB # :nodoc:
end
end
], nil, __FILE__, line
__send__ :#{cmd_name}_, *opts, &b
__send__ :#{cmd_name}_, *opts#{kwargs}, &b
end
], nil, __FILE__, line
else

View file

@ -280,6 +280,7 @@ module IRB
Reline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS
end
Reline.completion_append_character = nil
Reline.completer_quote_characters = ''
Reline.completion_proc = IRB::InputCompletor::CompletionProc
Reline.output_modifier_proc =
if IRB.conf[:USE_COLORIZE]

View file

@ -28,53 +28,8 @@ Gem::Specification.new do |spec|
"doc/irb/irb.rd.ja",
"exe/irb",
"irb.gemspec",
"lib/irb.rb",
"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",
"lib/irb/cmd/pushws.rb",
"lib/irb/cmd/subirb.rb",
"lib/irb/color.rb",
"lib/irb/color_printer.rb",
"lib/irb/completion.rb",
"lib/irb/context.rb",
"lib/irb/easter-egg.rb",
"lib/irb/ext/change-ws.rb",
"lib/irb/ext/history.rb",
"lib/irb/ext/loader.rb",
"lib/irb/ext/multi-irb.rb",
"lib/irb/ext/save-history.rb",
"lib/irb/ext/tracer.rb",
"lib/irb/ext/use-loader.rb",
"lib/irb/ext/workspaces.rb",
"lib/irb/extend-command.rb",
"lib/irb/frame.rb",
"lib/irb/help.rb",
"lib/irb/init.rb",
"lib/irb/input-method.rb",
"lib/irb/inspector.rb",
"lib/irb/lc/error.rb",
"lib/irb/lc/help-message",
"lib/irb/lc/ja/encoding_aliases.rb",
"lib/irb/lc/ja/error.rb",
"lib/irb/lc/ja/help-message",
"lib/irb/locale.rb",
"lib/irb/magic-file.rb",
"lib/irb/notifier.rb",
"lib/irb/output-method.rb",
"lib/irb/ruby-lex.rb",
"lib/irb/ruby_logo.aa",
"lib/irb/src_encoding.rb",
"lib/irb/version.rb",
"lib/irb/workspace.rb",
"lib/irb/ws-for-case-2.rb",
"lib/irb/xmp.rb",
"man/irb.1",
]
] + Dir.glob("lib/**/*")
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

View file

@ -10,7 +10,7 @@
#
#
Usage: irb.rb [options] [programfile] [arguments]
-f Suppress read of ~/.irbrc
-f Suppress read of ~/.irbrc
-d Set $DEBUG to true (same as `ruby -d')
-r load-module Same as `ruby -r'
-I path Specify $LOAD_PATH directory
@ -18,7 +18,7 @@ Usage: irb.rb [options] [programfile] [arguments]
-E enc Same as `ruby -E`
-w Same as `ruby -w`
-W[level=2] Same as `ruby -W`
--context-mode n Set n[0-3] to method to create Binding Object,
--context-mode n Set n[0-4] to method to create Binding Object,
when new workspace was created
--echo Show result(default)
--noecho Don't show result
@ -31,8 +31,8 @@ Usage: irb.rb [options] [programfile] [arguments]
--colorize Use colorization
--nocolorize Don't use colorization
--prompt prompt-mode/--prompt-mode prompt-mode
Switch prompt mode. Pre-defined prompt modes are
`default', `simple', `xmp' and `inf-ruby'
Switch prompt mode. Pre-defined prompt modes are
`default', `simple', `xmp' and `inf-ruby'
--inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
Suppresses --multiline and --singleline.
--sample-book-mode/--simple-prompt
@ -41,8 +41,8 @@ Usage: irb.rb [options] [programfile] [arguments]
--single-irb Share self with sub-irb.
--tracer Display trace for each execution of commands.
--back-trace-limit n
Display backtrace top n and tail n. The default
value is 16.
Display backtrace top n and tail n. The default
value is 16.
--verbose Show details
--noverbose Don't show details
-v, --version Print the version of irb

View file

@ -47,12 +47,26 @@ class RubyLex
@io = io
if @io.respond_to?(:check_termination)
@io.check_termination do |code|
code.gsub!(/\s*\z/, '').concat("\n")
ltype, indent, continue, code_block_open = check_state(code)
if ltype or indent > 0 or continue or code_block_open
false
if Reline::IOGate.in_pasting?
lex = RubyLex.new
rest = lex.check_termination_in_prev_line(code)
if rest
Reline.delete_text
rest.bytes.reverse_each do |c|
Reline.ungetc(c)
end
true
else
false
end
else
true
code.gsub!(/\s*\z/, '').concat("\n")
ltype, indent, continue, code_block_open = check_state(code)
if ltype or indent > 0 or continue or code_block_open
false
else
true
end
end
end
end
@ -60,7 +74,7 @@ class RubyLex
@io.dynamic_prompt do |lines|
lines << '' if lines.empty?
result = []
tokens = ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join)
tokens = self.class.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join)
code = String.new
partial_tokens = []
unprocessed_tokens = []
@ -115,10 +129,10 @@ class RubyLex
:on_param_error
]
def ripper_lex_without_warning(code)
def self.ripper_lex_without_warning(code)
verbose, $VERBOSE = $VERBOSE, nil
tokens = nil
self.class.compile_with_errors_suppressed(code) do |inner_code, line_no|
compile_with_errors_suppressed(code) do |inner_code, line_no|
lexer = Ripper::Lexer.new(inner_code, '-', line_no)
if lexer.respond_to?(:scan) # Ruby 2.7+
tokens = []
@ -168,7 +182,7 @@ class RubyLex
if @io.respond_to?(:auto_indent) and context.auto_indent_mode
@io.auto_indent do |lines, line_index, byte_pointer, is_newline|
if is_newline
@tokens = ripper_lex_without_warning(lines[0..line_index].join("\n"))
@tokens = self.class.ripper_lex_without_warning(lines[0..line_index].join("\n"))
prev_spaces = find_prev_spaces(line_index)
depth_difference = check_newline_depth_difference
depth_difference = 0 if depth_difference < 0
@ -177,7 +191,7 @@ class RubyLex
code = line_index.zero? ? '' : lines[0..(line_index - 1)].map{ |l| l + "\n" }.join
last_line = lines[line_index]&.byteslice(0, byte_pointer)
code += last_line if last_line
@tokens = ripper_lex_without_warning(code)
@tokens = self.class.ripper_lex_without_warning(code)
corresponding_token_depth = check_corresponding_token_depth
if corresponding_token_depth
corresponding_token_depth
@ -190,7 +204,7 @@ class RubyLex
end
def check_state(code, tokens = nil)
tokens = ripper_lex_without_warning(code) unless tokens
tokens = self.class.ripper_lex_without_warning(code) unless tokens
ltype = process_literal_type(tokens)
indent = process_nesting_level(tokens)
continue = process_continue(tokens)
@ -256,7 +270,7 @@ class RubyLex
end
code = @line + (line.nil? ? '' : line)
code.gsub!(/\s*\z/, '').concat("\n")
@tokens = ripper_lex_without_warning(code)
@tokens = self.class.ripper_lex_without_warning(code)
@continue = process_continue
@code_block_open = check_code_block(code)
@indent = process_nesting_level
@ -277,8 +291,9 @@ class RubyLex
return true
elsif tokens.size >= 1 and tokens[-1][1] == :on_heredoc_end # "EOH\n"
return false
elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2][3].anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME)
elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2][3].anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) and tokens[-2][2] !~ /\A\.\.\.?\z/
# end of literal except for regexp
# endless range at end of line is not a continue
return true
end
false
@ -738,5 +753,50 @@ class RubyLex
nil
end
end
def check_termination_in_prev_line(code)
tokens = self.class.ripper_lex_without_warning(code)
past_first_newline = false
index = tokens.rindex do |t|
# traverse first token before last line
if past_first_newline
if t.tok.include?("\n")
true
end
elsif t.tok.include?("\n")
past_first_newline = true
false
else
false
end
end
if index
first_token = nil
last_line_tokens = tokens[(index + 1)..(tokens.size - 1)]
last_line_tokens.each do |t|
unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event)
first_token = t
break
end
end
if first_token.nil?
return false
elsif first_token && first_token.state == Ripper::EXPR_DOT
return false
else
tokens_without_last_line = tokens[0..index]
ltype = process_literal_type(tokens_without_last_line)
indent = process_nesting_level(tokens_without_last_line)
continue = process_continue(tokens_without_last_line)
code_block_open = check_code_block(tokens_without_last_line.map(&:tok).join(''), tokens_without_last_line)
if ltype or indent > 0 or continue or code_block_open
return false
else
return last_line_tokens.map(&:tok).join('')
end
end
end
false
end
end
# :startdoc:

View file

@ -11,7 +11,7 @@
#
module IRB # :nodoc:
VERSION = "1.3.4"
VERSION = "1.3.5"
@RELEASE_VERSION = VERSION
@LAST_UPDATE_DATE = "2021-02-25"
@LAST_UPDATE_DATE = "2021-04-03"
end

View file

@ -446,6 +446,10 @@ module Reline
}
end
def self.ungetc(c)
Reline::IOGate.ungetc(c)
end
def self.line_editor
core.line_editor
end

View file

@ -813,6 +813,7 @@ class Reline::LineEditor
end
move_cursor_up(back)
move_cursor_down(@first_line_started_from + @started_from)
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
@ -1158,8 +1159,25 @@ class Reline::LineEditor
def call_completion_proc
result = retrieve_completion_block(true)
slice = result[1]
result = @completion_proc.(slice) if @completion_proc and slice
preposing, target, postposing = result
if @completion_proc and target
argnum = @completion_proc.parameters.inject(0) { |result, item|
case item.first
when :req, :opt
result + 1
when :rest
break 3
end
}
case argnum
when 1
result = @completion_proc.(target)
when 2
result = @completion_proc.(target, preposing)
when 3..Float::INFINITY
result = @completion_proc.(target, preposing, postposing)
end
end
Reline.core.instance_variable_set(:@completion_quote_character, nil)
result
end
@ -1207,8 +1225,16 @@ class Reline::LineEditor
end
def retrieve_completion_block(set_completion_quote_character = false)
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
if Reline.completer_word_break_characters.empty?
word_break_regexp = nil
else
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
end
if Reline.completer_quote_characters.empty?
quote_characters_regexp = nil
else
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
end
before = @line.byteslice(0, @byte_pointer)
rest = nil
break_pointer = nil
@ -1229,14 +1255,14 @@ class Reline::LineEditor
elsif quote and slice.start_with?(escaped_quote)
# skip
i += 2
elsif slice =~ quote_characters_regexp # find new "
elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
rest = $'
quote = $&
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
escaped_quote = /\\#{Regexp.escape(quote)}/
i += 1
break_pointer = i - 1
elsif not quote and slice =~ word_break_regexp
elsif word_break_regexp and not quote and slice =~ word_break_regexp
rest = $'
i += 1
before = @line.byteslice(i, @byte_pointer - i)
@ -1264,6 +1290,19 @@ class Reline::LineEditor
end
target = before
end
if @is_multiline
if @previous_line_index
lines = whole_lines(index: @previous_line_index, line: @line)
else
lines = whole_lines
end
if @line_index > 0
preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
end
if (lines.size - 1) > @line_index
postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
end
end
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
end
@ -1291,10 +1330,32 @@ class Reline::LineEditor
def delete_text(start = nil, length = nil)
if start.nil? and length.nil?
@line&.clear
@byte_pointer = 0
@cursor = 0
@cursor_max = 0
if @is_multiline
if @buffer_of_lines.size == 1
@line&.clear
@byte_pointer = 0
@cursor = 0
@cursor_max = 0
elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
@buffer_of_lines.pop
@line_index -= 1
@line = @buffer_of_lines[@line_index]
@byte_pointer = 0
@cursor = 0
@cursor_max = calculate_width(@line)
elsif @line_index < (@buffer_of_lines.size - 1)
@buffer_of_lines.delete_at(@line_index)
@line = @buffer_of_lines[@line_index]
@byte_pointer = 0
@cursor = 0
@cursor_max = calculate_width(@line)
end
else
@line&.clear
@byte_pointer = 0
@cursor = 0
@cursor_max = 0
end
elsif not start.nil? and not length.nil?
if @line
before = @line.byteslice(0, start)

View file

@ -1,3 +1,3 @@
module Reline
VERSION = '0.2.4'
VERSION = '0.2.5'
end

View file

@ -5,129 +5,6 @@ require "irb/extend-command"
module TestIRB
class ExtendCommand < Test::Unit::TestCase
def setup
@pwd = Dir.pwd
@tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}")
begin
Dir.mkdir(@tmpdir)
rescue Errno::EEXIST
FileUtils.rm_rf(@tmpdir)
Dir.mkdir(@tmpdir)
end
Dir.chdir(@tmpdir)
@home_backup = ENV["HOME"]
ENV["HOME"] = @tmpdir
@default_encoding = [Encoding.default_external, Encoding.default_internal]
@stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] }
IRB.instance_variable_get(:@CONF).clear
end
def teardown
ENV["HOME"] = @home_backup
Dir.chdir(@pwd)
FileUtils.rm_rf(@tmpdir)
EnvUtil.suppress_warning {
Encoding.default_external, Encoding.default_internal = *@default_encoding
[STDIN, STDOUT, STDERR].zip(@stdio_encodings) do |io, encs|
io.set_encoding(*encs)
end
}
end
def test_irb_info_multiline
FileUtils.touch("#{@tmpdir}/.inputrc")
FileUtils.touch("#{@tmpdir}/.irbrc")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = true
IRB.conf[:USE_SINGLELINE] = false
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace)
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sReidlineInputMethod\swith\sReline .+ and .+\n
\.irbrc\spath: .+\n
RUBY_PLATFORM: .+
}x
assert_match expected, irb.context.main.irb_info.to_s
end
def test_irb_info_singleline
FileUtils.touch("#{@tmpdir}/.inputrc")
FileUtils.touch("#{@tmpdir}/.irbrc")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = false
IRB.conf[:USE_SINGLELINE] = true
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace)
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sReadlineInputMethod\swith .+ and .+\n
\.irbrc\spath: .+\n
RUBY_PLATFORM: .+
}x
assert_match expected, irb.context.main.irb_info.to_s
end
def test_irb_info_multiline_without_rc_files
inputrc_backup = ENV["INPUTRC"]
ENV["INPUTRC"] = "unknown_inpurc"
ext_backup = IRB::IRBRC_EXT
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, "unknown_ext")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = true
IRB.conf[:USE_SINGLELINE] = false
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace)
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sReidlineInputMethod\swith\sReline\s[^ ]+(?!\sand\s.+)\n
RUBY_PLATFORM: .+\n
\z
}x
assert_match expected, irb.context.main.irb_info.to_s
ensure
ENV["INPUTRC"] = inputrc_backup
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, ext_backup)
end
def test_irb_info_singleline_without_rc_files
inputrc_backup = ENV["INPUTRC"]
ENV["INPUTRC"] = "unknown_inpurc"
ext_backup = IRB::IRBRC_EXT
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, "unknown_ext")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = false
IRB.conf[:USE_SINGLELINE] = true
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace)
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sReadlineInputMethod\swith\s(?~.*\sand\s.+)\n
RUBY_PLATFORM: .+\n
\z
}x
assert_match expected, irb.context.main.irb_info.to_s
ensure
ENV["INPUTRC"] = inputrc_backup
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, ext_backup)
end
class TestInputMethod < ::IRB::InputMethod
attr_reader :list, :line_no
@ -154,6 +31,131 @@ module TestIRB
end
end
def setup
@pwd = Dir.pwd
@tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}")
begin
Dir.mkdir(@tmpdir)
rescue Errno::EEXIST
FileUtils.rm_rf(@tmpdir)
Dir.mkdir(@tmpdir)
end
Dir.chdir(@tmpdir)
@home_backup = ENV["HOME"]
ENV["HOME"] = @tmpdir
@xdg_config_home_backup = ENV.delete("XDG_CONFIG_HOME")
@default_encoding = [Encoding.default_external, Encoding.default_internal]
@stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] }
IRB.instance_variable_get(:@CONF).clear
end
def teardown
ENV["XDG_CONFIG_HOME"] = @xdg_config_home_backup
ENV["HOME"] = @home_backup
Dir.chdir(@pwd)
FileUtils.rm_rf(@tmpdir)
EnvUtil.suppress_warning {
Encoding.default_external, Encoding.default_internal = *@default_encoding
[STDIN, STDOUT, STDERR].zip(@stdio_encodings) do |io, encs|
io.set_encoding(*encs)
end
}
end
def test_irb_info_multiline
FileUtils.touch("#{@tmpdir}/.inputrc")
FileUtils.touch("#{@tmpdir}/.irbrc")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = true
IRB.conf[:USE_SINGLELINE] = false
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace, TestInputMethod.new([]))
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sAbstract\sInputMethod\n
\.irbrc\spath: .+\n
RUBY_PLATFORM: .+
}x
assert_match expected, irb.context.main.irb_info.to_s
end
def test_irb_info_singleline
FileUtils.touch("#{@tmpdir}/.inputrc")
FileUtils.touch("#{@tmpdir}/.irbrc")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = false
IRB.conf[:USE_SINGLELINE] = true
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace, TestInputMethod.new([]))
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sAbstract\sInputMethod\n
\.irbrc\spath: .+\n
RUBY_PLATFORM: .+
}x
assert_match expected, irb.context.main.irb_info.to_s
end
def test_irb_info_multiline_without_rc_files
inputrc_backup = ENV["INPUTRC"]
ENV["INPUTRC"] = "unknown_inpurc"
ext_backup = IRB::IRBRC_EXT
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, "unknown_ext")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = true
IRB.conf[:USE_SINGLELINE] = false
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace, TestInputMethod.new([]))
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sAbstract\sInputMethod\n
RUBY_PLATFORM: .+\n
\z
}x
assert_match expected, irb.context.main.irb_info.to_s
ensure
ENV["INPUTRC"] = inputrc_backup
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, ext_backup)
end
def test_irb_info_singleline_without_rc_files
inputrc_backup = ENV["INPUTRC"]
ENV["INPUTRC"] = "unknown_inpurc"
ext_backup = IRB::IRBRC_EXT
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, "unknown_ext")
IRB.setup(__FILE__, argv: [])
IRB.conf[:USE_MULTILINE] = false
IRB.conf[:USE_SINGLELINE] = true
IRB.conf[:VERBOSE] = false
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace, TestInputMethod.new([]))
IRB.conf[:MAIN_CONTEXT] = irb.context
expected = %r{
Ruby\sversion: .+\n
IRB\sversion:\sirb .+\n
InputMethod:\sAbstract\sInputMethod\n
RUBY_PLATFORM: .+\n
\z
}x
assert_match expected, irb.context.main.irb_info.to_s
ensure
ENV["INPUTRC"] = inputrc_backup
IRB.__send__(:remove_const, :IRBRC_EXT)
IRB.const_set(:IRBRC_EXT, ext_backup)
end
def test_measure
IRB.init_config(nil)
IRB.conf[:PROMPT] = {
@ -372,5 +374,56 @@ module TestIRB
/=> "bug17564"\n/,
], out)
end
def test_ls
input = TestInputMethod.new([
"ls Object.new.tap { |o| o.instance_variable_set(:@a, 1) }\n",
])
IRB.init_config(nil)
workspace = IRB::WorkSpace.new(self)
IRB.conf[:VERBOSE] = false
irb = IRB::Irb.new(workspace, input)
IRB.conf[:MAIN_CONTEXT] = irb.context
irb.context.return_format = "=> %s\n"
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(/^instance variables:\s+@a\n/m, out)
end
def test_show_source
input = TestInputMethod.new([
"show_source 'IRB.conf'\n",
])
IRB.init_config(nil)
workspace = IRB::WorkSpace.new(self)
irb = IRB::Irb.new(workspace, input)
IRB.conf[:VERBOSE] = false
IRB.conf[:MAIN_CONTEXT] = irb.context
irb.context.return_format = "=> %s\n"
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(%r[/irb\.rb], out)
end
def test_whereami
input = TestInputMethod.new([
"whereami\n",
])
IRB.init_config(nil)
workspace = IRB::WorkSpace.new(self)
IRB.conf[:VERBOSE] = false
irb = IRB::Irb.new(workspace, input)
IRB.conf[:MAIN_CONTEXT] = irb.context
irb.context.return_format = "=> %s\n"
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(/^From: .+ @ line \d+ :\n/, out)
end
end
end

View file

@ -66,6 +66,7 @@ module TestIRB
"\t" => "\t", # not ^I
"foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})",
"$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}",
"__END__" => "#{GREEN}__END__#{CLEAR}",
}
# specific to Ruby 2.7+

View file

@ -34,6 +34,7 @@ module TestIRB
end
{
1 => "#{BLUE}#{BOLD}1#{CLEAR}\n",
"a\nb" => %[#{RED}#{BOLD}"#{CLEAR}#{RED}a\\nb#{CLEAR}#{RED}#{BOLD}"#{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",

View file

@ -55,5 +55,33 @@ module TestIRB
namespace = IRB::InputCompletor.retrieve_completion_data("1.positive?", bind: binding, doc_namespace: true)
assert_equal "Integer.positive?", namespace
end
def test_complete_require
candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "")
%w['irb/init 'irb/ruby-lex].each do |word|
assert_include candidates, word
end
# Test cache
candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "")
%w['irb/init 'irb/ruby-lex].each do |word|
assert_include candidates, word
end
end
def test_complete_require_relative
candidates = Dir.chdir(__dir__ + "/../..") do
IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "")
end
%w['lib/irb/init 'lib/irb/ruby-lex].each do |word|
assert_include candidates, word
end
# Test cache
candidates = Dir.chdir(__dir__ + "/../..") do
IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "")
end
%w['lib/irb/init 'lib/irb/ruby-lex].each do |word|
assert_include candidates, word
end
end
end
end

View file

@ -127,11 +127,43 @@ module TestIRB
INPUT
end
def test_history_concurrent_use
omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
IRB.conf[:SAVE_HISTORY] = 1
assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) do |history_file|
exit
5
exit
EXPECTED_HISTORY
1
2
3
4
INITIAL_HISTORY
5
exit
INPUT
assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2)
exit
EXPECTED_HISTORY2
1
2
3
4
INITIAL_HISTORY2
5
exit
INPUT2
File.utime(File.atime(history_file), File.mtime(history_file) + 2, history_file)
end
end
private
def assert_history(expected_history, initial_irb_history, input)
backup_verbose, $VERBOSE = $VERBOSE, nil
backup_home = ENV["HOME"]
backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
IRB.conf[:LC_MESSAGES] = IRB::Locale.new
actual_history = nil
Dir.mktmpdir("test_irb_history_#{$$}") do |tmpdir|
@ -143,6 +175,11 @@ module TestIRB
io = TestInputMethod.new
io.class::HISTORY.clear
io.load_history
if block_given?
history = io.class::HISTORY.dup
yield IRB.rc_file("_history")
io.class::HISTORY.replace(history)
end
io.class::HISTORY.concat(input.split)
io.save_history
@ -160,6 +197,7 @@ module TestIRB
ensure
$VERBOSE = backup_verbose
ENV["HOME"] = backup_home
ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home
end
def with_temp_stdio

View file

@ -64,6 +64,12 @@ module TestIRB
ENV["IRBRC"] = backup_irbrc
end
def test_recovery_sigint
bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //)
Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled?
end
private
def with_argv(argv)

View file

@ -136,6 +136,20 @@ module TestIRB
end
end
def test_endless_range_at_end_of_line
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.6.0')
skip 'Endless range is available in 2.6.0 or later'
end
input_with_prompt = [
PromptRow.new('001:0: :> ', %q(a = 3..)),
PromptRow.new('002:0: :* ', %q()),
]
lines = input_with_prompt.map(&:content)
expected_prompt_list = input_with_prompt.map(&:prompt)
assert_dynamic_prompt(lines, expected_prompt_list)
end
def test_incomplete_coding_magic_comment
input_with_correct_indents = [
Row.new(%q(#coding:u), nil, 0),
@ -544,8 +558,7 @@ module TestIRB
skip 'This test needs Ripper::Lexer#scan to take broken tokens'
end
ruby_lex = RubyLex.new
tokens = ruby_lex.ripper_lex_without_warning('%wwww')
tokens = RubyLex.ripper_lex_without_warning('%wwww')
pos_to_index = {}
tokens.each_with_index { |t, i|
assert_nil(pos_to_index[t[0]], "There is already another token in the position of #{t.inspect}.")
@ -558,8 +571,7 @@ module TestIRB
skip 'This test needs Ripper::Lexer#scan to take broken tokens'
end
ruby_lex = RubyLex.new
tokens = ruby_lex.ripper_lex_without_warning(<<~EOC.chomp)
tokens = RubyLex.ripper_lex_without_warning(<<~EOC.chomp)
def foo
%wwww
end

View file

@ -0,0 +1,165 @@
require 'irb'
begin
require 'yamatanooroti'
class IRB::TestRendering < Yamatanooroti::TestCase
def setup
@pwd = Dir.pwd
suffix = '%010d' % Random.rand(0..65535)
@tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_irb_#{$$}_#{suffix}")
begin
Dir.mkdir(@tmpdir)
rescue Errno::EEXIST
FileUtils.rm_rf(@tmpdir)
Dir.mkdir(@tmpdir)
end
@irbrc_backup = ENV['IRBRC']
@irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc')
File.unlink(@irbrc_file) if File.exist?(@irbrc_file)
end
def teardown
FileUtils.rm_rf(@tmpdir)
ENV['IRBRC'] = @irbrc_backup
ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT']
end
def test_launch
write_irbrc <<~'LINES'
puts 'start IRB'
LINES
start_terminal(25, 80, %W{ruby -I#{@pwd}/lib -I#{@pwd}/../reline/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write(<<~EOC)
'Hello, World!'
EOC
close
assert_screen(<<~EOC)
start IRB
irb(main):001:0> 'Hello, World!'
=> "Hello, World!"
irb(main):002:0>
EOC
end
def test_multiline_paste
write_irbrc <<~'LINES'
puts 'start IRB'
LINES
start_terminal(25, 80, %W{ruby -I#{@pwd}/lib -I#{@pwd}/../reline/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write(<<~EOC)
class A
def inspect; '#<A>'; end
def a; self; end
def b; true; end
end
a = A.new
a
.a
.b
EOC
close
assert_screen(<<~EOC)
start IRB
irb(main):001:1* class A
irb(main):002:1* def inspect; '#<A>'; end
irb(main):003:1* def a; self; end
irb(main):004:1* def b; true; end
irb(main):005:0> end
=> :b
irb(main):006:0>
irb(main):007:0> a = A.new
=> #<A>
irb(main):008:0>
irb(main):009:0> a
irb(main):010:0> .a
irb(main):011:0> .b
=> true
irb(main):012:0>
EOC
end
def test_evaluate_each_toplevel_statement_by_multiline_paste
write_irbrc <<~'LINES'
puts 'start IRB'
LINES
start_terminal(40, 80, %W{ruby -I#{@pwd}/lib -I#{@pwd}/../reline/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
write(<<~EOC)
class A
def inspect; '#<A>'; end
def b; self; end
def c; true; end
end
a = A.new
a
.b
# aaa
.c
(a)
&.b()
class A def b; self; end; def c; true; end; end;
a = A.new
a
.b
# aaa
.c
(a)
&.b()
EOC
close
assert_screen(<<~EOC)
start IRB
irb(main):001:1* class A
irb(main):002:1* def inspect; '#<A>'; end
irb(main):003:1* def b; self; end
irb(main):004:1* def c; true; end
irb(main):005:0> end
=> :c
irb(main):006:0>
irb(main):007:0> a = A.new
=> #<A>
irb(main):008:0>
irb(main):009:0> a
irb(main):010:0> .b
irb(main):011:0> # aaa
irb(main):012:0> .c
=> true
irb(main):013:0>
irb(main):014:0> (a)
irb(main):015:0> &.b()
=> #<A>
irb(main):016:0>
irb(main):017:0>
irb(main):018:0> class A def b; self; end; def c; true; end; end;
=> :c
irb(main):019:0> a = A.new
=> #<A>
irb(main):020:0> a
irb(main):021:0> .b
irb(main):022:0> # aaa
irb(main):023:0> .c
=> true
irb(main):024:0> (a)
irb(main):025:0> &.b()
=> #<A>
irb(main):026:0>
EOC
end
private def write_irbrc(content)
File.open(@irbrc_file, 'w') do |f|
f.write content
end
end
end
rescue LoadError, NameError
# On Ruby repository, this test suit doesn't run because Ruby repo doesn't
# have the yamatanooroti gem.
end

View file

@ -65,6 +65,8 @@ class Reline::Test < Reline::TestCase
Reline.completer_word_break_characters = "[".encode(Encoding::ASCII)
assert_equal("[", Reline.completer_word_break_characters)
assert_equal(get_reline_encoding, Reline.completer_word_break_characters.encoding)
assert_nothing_raised { Reline.completer_word_break_characters = '' }
ensure
Reline.completer_word_break_characters = completer_word_break_characters
end
@ -89,6 +91,8 @@ class Reline::Test < Reline::TestCase
Reline.completer_quote_characters = "`".encode(Encoding::ASCII)
assert_equal("`", Reline.completer_quote_characters)
assert_equal(get_reline_encoding, Reline.completer_quote_characters.encoding)
assert_nothing_raised { Reline.completer_quote_characters = '' }
ensure
Reline.completer_quote_characters = completer_quote_characters
end

View file

@ -20,4 +20,58 @@ class Reline::LineEditor::StringProcessingTest < Reline::TestCase
width = @line_editor.send(:calculate_width, "\1\e[31m\2RubyColor\1\e[34m\2 default string \1\e[m\2>", true)
assert_equal('RubyColor default string >'.size, width)
end
def test_completion_proc_with_preposing_and_postposing
buf = ['def hoge', ' puts :aaa', 'end']
@line_editor.instance_variable_set(:@is_multiline, true)
@line_editor.instance_variable_set(:@buffer_of_lines, buf)
@line_editor.instance_variable_set(:@line, buf[1])
@line_editor.instance_variable_set(:@byte_pointer, 3)
@line_editor.instance_variable_set(:@cursor, 3)
@line_editor.instance_variable_set(:@cursor_max, 11)
@line_editor.instance_variable_set(:@line_index, 1)
@line_editor.instance_variable_set(:@completion_proc, proc { |target|
assert_equal('p', target)
})
@line_editor.__send__(:call_completion_proc)
@line_editor.instance_variable_set(:@is_multiline, true)
@line_editor.instance_variable_set(:@buffer_of_lines, buf)
@line_editor.instance_variable_set(:@line, buf[1])
@line_editor.instance_variable_set(:@byte_pointer, 6)
@line_editor.instance_variable_set(:@cursor, 6)
@line_editor.instance_variable_set(:@cursor_max, 11)
@line_editor.instance_variable_set(:@line_index, 1)
@line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post|
assert_equal('puts', target)
assert_equal("def hoge\n ", pre)
assert_equal(" :aaa\nend", post)
})
@line_editor.__send__(:call_completion_proc)
@line_editor.instance_variable_set(:@line, buf[0])
@line_editor.instance_variable_set(:@byte_pointer, 6)
@line_editor.instance_variable_set(:@cursor, 6)
@line_editor.instance_variable_set(:@cursor_max, 8)
@line_editor.instance_variable_set(:@line_index, 0)
@line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post|
assert_equal('ho', target)
assert_equal('def ', pre)
assert_equal("ge\n puts :aaa\nend", post)
})
@line_editor.__send__(:call_completion_proc)
@line_editor.instance_variable_set(:@line, buf[2])
@line_editor.instance_variable_set(:@byte_pointer, 1)
@line_editor.instance_variable_set(:@cursor, 1)
@line_editor.instance_variable_set(:@cursor_max, 3)
@line_editor.instance_variable_set(:@line_index, 2)
@line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post|
assert_equal('e', target)
assert_equal("def hoge\n puts :aaa\n", pre)
assert_equal('nd', post)
})
@line_editor.__send__(:call_completion_proc)
end
end

View file

@ -59,4 +59,17 @@ class Reline::WithinPipeTest < Reline::TestCase
@writer.write("abcde\C-b\C-b\C-b\C-x\C-d\C-x\C-h\C-x\C-v\C-a\C-f\C-f EF\C-x\C-t gh\C-x\M-t\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-x\M-u\C-x\M-l\C-x\M-c\n")
assert_equal "a\C-aDE gh Fe", Reline.readmultiline(&proc{ true })
end
def test_delete_text_in_multiline
@writer.write("abc\ndef\nxyz\n")
result = Reline.readmultiline(&proc{ |str|
if str.include?('xyz')
Reline.delete_text
true
else
false
end
})
assert_equal "abc\ndef", result
end
end

View file

@ -719,6 +719,17 @@ begin
EOC
end
def test_reset_rest_height_when_clear_screen
start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
write("\n\n\n\C-l3\n")
close
assert_screen(<<~EOC)
prompt> 3
=> 3
prompt>
EOC
end
private def write_inputrc(content)
File.open(@inputrc_file, 'w') do |f|
f.write content