summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomoya ishida <tomoyapenguin@gmail.com>2023-10-12 02:08:59 +0900
committergit <svn-admin@ruby-lang.org>2023-10-11 17:09:05 +0000
commit94cb5765e2e44716800cd466c64f81c048aaf95b (patch)
tree8e372dac7478a36582b9de656731361924515788
parentb9a6fca67d97f7319b20b24427d6dc4b8290cd24 (diff)
[ruby/irb] Rename current completor to RegexpCompletor and
refactored for future extension (https://github.com/ruby/irb/pull/707) * Move completion implementation to completion/regexp_completor for future extension * Remove constant CompletionProc and PerfectMatchedProc and add a class method * Move document display logic to InputCompletor. Each completor only need to implement `completion_caididates` and `doc_namespace` * Move display_document logic to RelineInputMethod * Use RegexpCompletor directly. Not through class method of InputCompletor. * RegexpCompletor extends BaseCompletor, move back definition to completion.rb * Move display_document test to input_method test * Stop re-initialize completor on each completion phase * Store completor to ReadlineInputMethod's iver https://github.com/ruby/irb/commit/1e98521483
-rw-r--r--lib/irb/completion.rb174
-rw-r--r--lib/irb/input-method.rb213
-rw-r--r--test/irb/test_completion.rb222
-rw-r--r--test/irb/test_input_method.rb104
4 files changed, 376 insertions, 337 deletions
diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb
index a143d1b3e1..26215f6fe1 100644
--- a/lib/irb/completion.rb
+++ b/lib/irb/completion.rb
@@ -8,55 +8,14 @@
require_relative 'ruby-lex'
module IRB
- module InputCompletor # :nodoc:
- using Module.new {
- refine ::Binding do
- def eval_methods
- ::Kernel.instance_method(:methods).bind(eval("self")).call
- end
-
- def eval_private_methods
- ::Kernel.instance_method(:private_methods).bind(eval("self")).call
- end
-
- def eval_instance_variables
- ::Kernel.instance_method(:instance_variables).bind(eval("self")).call
- end
-
- def eval_global_variables
- ::Kernel.instance_method(:global_variables).bind(eval("self")).call
- end
-
- def eval_class_constants
- ::Module.instance_method(:constants).bind(eval("self.class")).call
- end
- end
- }
-
- # Set of reserved words used by Ruby, you should not use these for
- # constants or variables
- ReservedWords = %w[
- __ENCODING__ __LINE__ __FILE__
- BEGIN END
- alias and
- begin break
- case class
- def defined? do
- else elsif end ensure
- false for
- if in
- module
- next nil not
- or
- redo rescue retry return
- self super
- then true
- undef unless until
- when while
- yield
- ]
+ class BaseCompletor # :nodoc:
+ def completion_candidates(preposing, target, postposing, bind:)
+ raise NotImplementedError
+ end
- BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{("
+ def doc_namespace(preposing, matched, postposing, bind:)
+ raise NotImplementedError
+ end
GEM_PATHS =
if defined?(Gem::Specification)
@@ -73,7 +32,7 @@ module IRB
[]
end.freeze
- def self.retrieve_gem_and_system_load_path
+ def retrieve_gem_and_system_load_path
candidates = (GEM_PATHS | $LOAD_PATH)
candidates.map do |p|
if p.respond_to?(:to_path)
@@ -84,8 +43,8 @@ module IRB
end.compact.sort
end
- def self.retrieve_files_to_require_from_load_path
- @@files_from_load_path ||=
+ def retrieve_files_to_require_from_load_path
+ @files_from_load_path ||=
(
shortest = []
rest = retrieve_gem_and_system_load_path.each_with_object([]) { |path, result|
@@ -103,13 +62,62 @@ module IRB
)
end
- def self.retrieve_files_to_require_relative_from_current_dir
- @@files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path|
+ def 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
+ end
- CompletionRequireProc = lambda { |target, preposing = nil, postposing = nil|
+ class RegexpCompletor < BaseCompletor # :nodoc:
+ using Module.new {
+ refine ::Binding do
+ def eval_methods
+ ::Kernel.instance_method(:methods).bind(eval("self")).call
+ end
+
+ def eval_private_methods
+ ::Kernel.instance_method(:private_methods).bind(eval("self")).call
+ end
+
+ def eval_instance_variables
+ ::Kernel.instance_method(:instance_variables).bind(eval("self")).call
+ end
+
+ def eval_global_variables
+ ::Kernel.instance_method(:global_variables).bind(eval("self")).call
+ end
+
+ def eval_class_constants
+ ::Module.instance_method(:constants).bind(eval("self.class")).call
+ end
+ end
+ }
+
+ # Set of reserved words used by Ruby, you should not use these for
+ # constants or variables
+ ReservedWords = %w[
+ __ENCODING__ __LINE__ __FILE__
+ BEGIN END
+ alias and
+ begin break
+ case class
+ def defined? do
+ else elsif end ensure
+ false for
+ if in
+ module
+ next nil not
+ or
+ redo rescue retry return
+ self super
+ then true
+ undef unless until
+ when while
+ yield
+ ]
+
+ def complete_require_path(target, preposing, postposing)
if target =~ /\A(['"])([^'"]+)\Z/
quote = $1
actual_target = $2
@@ -142,21 +150,21 @@ module IRB
end
end
result
- }
+ end
- CompletionProc = lambda { |target, preposing = nil, postposing = nil|
+ def completion_candidates(preposing, target, postposing, bind:)
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) }
+ result = complete_require_path(target, preposing, postposing)
+ return result if result
end
- }
+ retrieve_completion_data(target, bind: bind, doc_namespace: false).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)
+ def doc_namespace(_preposing, matched, _postposing, bind:)
+ retrieve_completion_data(matched, bind: bind, doc_namespace: true)
+ end
+
+ def retrieve_completion_data(input, bind:, doc_namespace:)
case input
# this regexp only matches the closing character because of irb's Reline.completer_quote_characters setting
# details are described in: https://github.com/ruby/irb/pull/523
@@ -394,44 +402,10 @@ module IRB
end
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|
- begin
- RDocRIDriver.add_method(out, m)
- rescue RDoc::RI::Driver::NotFoundError
- end
- end
- RDocRIDriver.display(out)
- else
- begin
- RDocRIDriver.display_names([namespace])
- rescue RDoc::RI::Driver::NotFoundError
- end
- end
- }
-
# Set of available operators in Ruby
Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~]
- def self.select_message(receiver, message, candidates, sep = ".")
+ def select_message(receiver, message, candidates, sep = ".")
candidates.grep(/^#{Regexp.quote(message)}/).collect do |e|
case e
when /^[a-zA-Z_]/
diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb
index bd7d4fede4..454b6db00b 100644
--- a/lib/irb/input-method.rb
+++ b/lib/irb/input-method.rb
@@ -11,6 +11,8 @@ require 'reline'
module IRB
class InputMethod
+ BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{("
+
# The irb prompt associated with this input method
attr_accessor :prompt
@@ -179,12 +181,16 @@ module IRB
super
@eof = false
+ @completor = RegexpCompletor.new
if Readline.respond_to?("basic_word_break_characters=")
- Readline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS
+ Readline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS
end
Readline.completion_append_character = nil
- Readline.completion_proc = IRB::InputCompletor::CompletionProc
+ Readline.completion_proc = ->(target) {
+ bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
+ @completor.completion_candidates('', target, '', bind: bind)
+ }
end
# Reads the next line from this input method.
@@ -230,11 +236,16 @@ module IRB
super
@eof = false
+ @completor = RegexpCompletor.new
- Reline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS
+ Reline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS
Reline.completion_append_character = nil
Reline.completer_quote_characters = ''
- Reline.completion_proc = IRB::InputCompletor::CompletionProc
+ Reline.completion_proc = ->(target, preposing, postposing) {
+ bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
+ @completion_params = [preposing, target, postposing, bind]
+ @completor.completion_candidates(preposing, target, postposing, bind: bind)
+ }
Reline.output_modifier_proc =
if IRB.conf[:USE_COLORIZE]
proc do |output, complete: |
@@ -247,13 +258,13 @@ module IRB
Reline::Unicode.escape_for_print(output)
end
end
- Reline.dig_perfect_match_proc = IRB::InputCompletor::PerfectMatchedProc
+ Reline.dig_perfect_match_proc = ->(matched) { display_document(matched) }
Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE]
if IRB.conf[:USE_AUTOCOMPLETE]
begin
require 'rdoc'
- Reline.add_dialog_proc(:show_doc, SHOW_DOC_DIALOG, Reline::DEFAULT_DIALOG_CONTEXT)
+ Reline.add_dialog_proc(:show_doc, show_doc_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT)
rescue LoadError
end
end
@@ -271,100 +282,140 @@ module IRB
@auto_indent_proc = block
end
- SHOW_DOC_DIALOG = ->() {
- dialog.trap_key = nil
- alt_d = [
- [Reline::Key.new(nil, 0xE4, true)], # Normal Alt+d.
- [27, 100], # Normal Alt+d when convert-meta isn't used.
- [195, 164], # The "ä" that appears when Alt+d is pressed on xterm.
- [226, 136, 130] # The "∂" that appears when Alt+d in pressed on iTerm2.
- ]
+ def show_doc_dialog_proc
+ doc_namespace = ->(matched) {
+ preposing, _target, postposing, bind = @completion_params
+ @completor.doc_namespace(preposing, matched, postposing, bind: bind)
+ }
+ ->() {
+ dialog.trap_key = nil
+ alt_d = [
+ [Reline::Key.new(nil, 0xE4, true)], # Normal Alt+d.
+ [27, 100], # Normal Alt+d when convert-meta isn't used.
+ [195, 164], # The "ä" that appears when Alt+d is pressed on xterm.
+ [226, 136, 130] # The "∂" that appears when Alt+d in pressed on iTerm2.
+ ]
+
+ if just_cursor_moving and completion_journey_data.nil?
+ return nil
+ end
+ cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4)
+ return nil if result.nil? or pointer.nil? or pointer < 0
- if just_cursor_moving and completion_journey_data.nil?
- return nil
- end
- cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4)
- return nil if result.nil? or pointer.nil? or pointer < 0
- name = result[pointer]
- name = IRB::InputCompletor.retrieve_completion_data(name, doc_namespace: true)
+ name = doc_namespace.call(result[pointer])
- options = {}
- options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty?
- driver = RDoc::RI::Driver.new(options)
+ options = {}
+ options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty?
+ driver = RDoc::RI::Driver.new(options)
- if key.match?(dialog.name)
- begin
- driver.display_names([name])
- rescue RDoc::RI::Driver::NotFoundError
+ if key.match?(dialog.name)
+ begin
+ driver.display_names([name])
+ rescue RDoc::RI::Driver::NotFoundError
+ end
end
- end
- begin
- name = driver.expand_name(name)
- rescue RDoc::RI::Driver::NotFoundError
- return nil
- rescue
- return nil # unknown error
- end
- doc = nil
- used_for_class = false
- if not name =~ /#|\./
- found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name)
- if not found.empty?
- doc = driver.class_document(name, found, klasses, includes, extends)
- used_for_class = true
- end
- end
- unless used_for_class
- doc = RDoc::Markup::Document.new
begin
- driver.add_method(doc, name)
+ name = driver.expand_name(name)
rescue RDoc::RI::Driver::NotFoundError
- doc = nil
+ return nil
rescue
return nil # unknown error
end
- end
- return nil if doc.nil?
- width = 40
-
- right_x = cursor_pos_to_render.x + autocomplete_dialog.width
- if right_x + width > screen_width
- right_width = screen_width - (right_x + 1)
- left_x = autocomplete_dialog.column - width
- left_x = 0 if left_x < 0
- left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width
- if right_width.positive? and left_width.positive?
- if right_width >= left_width
+ doc = nil
+ used_for_class = false
+ if not name =~ /#|\./
+ found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name)
+ if not found.empty?
+ doc = driver.class_document(name, found, klasses, includes, extends)
+ used_for_class = true
+ end
+ end
+ unless used_for_class
+ doc = RDoc::Markup::Document.new
+ begin
+ driver.add_method(doc, name)
+ rescue RDoc::RI::Driver::NotFoundError
+ doc = nil
+ rescue
+ return nil # unknown error
+ end
+ end
+ return nil if doc.nil?
+ width = 40
+
+ right_x = cursor_pos_to_render.x + autocomplete_dialog.width
+ if right_x + width > screen_width
+ right_width = screen_width - (right_x + 1)
+ left_x = autocomplete_dialog.column - width
+ left_x = 0 if left_x < 0
+ left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width
+ if right_width.positive? and left_width.positive?
+ if right_width >= left_width
+ width = right_width
+ x = right_x
+ else
+ width = left_width
+ x = left_x
+ end
+ elsif right_width.positive? and left_width <= 0
width = right_width
x = right_x
- else
+ elsif right_width <= 0 and left_width.positive?
width = left_width
x = left_x
+ else # Both are negative width.
+ return nil
end
- elsif right_width.positive? and left_width <= 0
- width = right_width
+ else
x = right_x
- elsif right_width <= 0 and left_width.positive?
- width = left_width
- x = left_x
- else # Both are negative width.
- return nil
end
+ formatter = RDoc::Markup::ToAnsi.new
+ formatter.width = width
+ dialog.trap_key = alt_d
+ mod_key = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt"
+ message = "Press #{mod_key}+d to read the full document"
+ contents = [message] + doc.accept(formatter).split("\n")
+ contents = contents.take(preferred_dialog_height)
+
+ y = cursor_pos_to_render.y
+ Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49')
+ }
+ end
+
+ def display_document(matched, driver: nil)
+ begin
+ require 'rdoc'
+ rescue LoadError
+ return
+ end
+
+ if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
+ IRB.__send__(:easter_egg)
+ return
+ end
+
+ _target, preposing, postposing, bind = @completion_params
+ namespace = @completor.doc_namespace(preposing, matched, postposing, bind: bind)
+ return unless namespace
+
+ driver ||= RDoc::RI::Driver.new
+ if namespace.is_a?(Array)
+ out = RDoc::Markup::Document.new
+ namespace.each do |m|
+ begin
+ driver.add_method(out, m)
+ rescue RDoc::RI::Driver::NotFoundError
+ end
+ end
+ driver.display(out)
else
- x = right_x
+ begin
+ driver.display_names([namespace])
+ rescue RDoc::RI::Driver::NotFoundError
+ end
end
- formatter = RDoc::Markup::ToAnsi.new
- formatter.width = width
- dialog.trap_key = alt_d
- mod_key = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt"
- message = "Press #{mod_key}+d to read the full document"
- contents = [message] + doc.accept(formatter).split("\n")
- contents = contents.take(preferred_dialog_height)
-
- y = cursor_pos_to_render.y
- Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49')
- }
+ end
# Reads the next line from this input method.
#
diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb
index 2a659818e3..2ef8938269 100644
--- a/test/irb/test_completion.rb
+++ b/test/irb/test_completion.rb
@@ -8,71 +8,83 @@ module TestIRB
class CompletionTest < TestCase
def setup
# make sure require completion candidates are not cached
- IRB::InputCompletor.class_variable_set(:@@files_from_load_path, nil)
+ IRB::BaseCompletor.class_variable_set(:@@files_from_load_path, nil)
+ end
+
+ def teardown
+ IRB.conf[:MAIN_CONTEXT] = nil
+ end
+
+ def completion_candidates(target, bind)
+ IRB::RegexpCompletor.new.completion_candidates('', target, '', bind: bind)
+ end
+
+ def doc_namespace(target, bind)
+ IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind)
end
class MethodCompletionTest < CompletionTest
def test_complete_string
- assert_include(IRB::InputCompletor.retrieve_completion_data("'foo'.up", bind: binding), "'foo'.upcase")
+ assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase")
# completing 'foo bar'.up
- assert_include(IRB::InputCompletor.retrieve_completion_data("bar'.up", bind: binding), "bar'.upcase")
- assert_equal("String.upcase", IRB::InputCompletor.retrieve_completion_data("'foo'.upcase", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("bar'.up", binding), "bar'.upcase")
+ assert_equal("String.upcase", doc_namespace("'foo'.upcase", binding))
end
def test_complete_regexp
- assert_include(IRB::InputCompletor.retrieve_completion_data("/foo/.ma", bind: binding), "/foo/.match")
+ assert_include(completion_candidates("/foo/.ma", binding), "/foo/.match")
# completing /foo bar/.ma
- assert_include(IRB::InputCompletor.retrieve_completion_data("bar/.ma", bind: binding), "bar/.match")
- assert_equal("Regexp.match", IRB::InputCompletor.retrieve_completion_data("/foo/.match", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("bar/.ma", binding), "bar/.match")
+ assert_equal("Regexp.match", doc_namespace("/foo/.match", binding))
end
def test_complete_array
- assert_include(IRB::InputCompletor.retrieve_completion_data("[].an", bind: binding), "[].any?")
- assert_equal("Array.any?", IRB::InputCompletor.retrieve_completion_data("[].any?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("[].an", binding), "[].any?")
+ assert_equal("Array.any?", doc_namespace("[].any?", binding))
end
def test_complete_hash_and_proc
# hash
- assert_include(IRB::InputCompletor.retrieve_completion_data("{}.an", bind: binding), "{}.any?")
- assert_equal(["Proc.any?", "Hash.any?"], IRB::InputCompletor.retrieve_completion_data("{}.any?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("{}.an", binding), "{}.any?")
+ assert_equal(["Proc.any?", "Hash.any?"], doc_namespace("{}.any?", binding))
# proc
- assert_include(IRB::InputCompletor.retrieve_completion_data("{}.bin", bind: binding), "{}.binding")
- assert_equal(["Proc.binding", "Hash.binding"], IRB::InputCompletor.retrieve_completion_data("{}.binding", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("{}.bin", binding), "{}.binding")
+ assert_equal(["Proc.binding", "Hash.binding"], doc_namespace("{}.binding", binding))
end
def test_complete_numeric
- assert_include(IRB::InputCompletor.retrieve_completion_data("1.positi", bind: binding), "1.positive?")
- assert_equal("Integer.positive?", IRB::InputCompletor.retrieve_completion_data("1.positive?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("1.positi", binding), "1.positive?")
+ assert_equal("Integer.positive?", doc_namespace("1.positive?", binding))
- assert_include(IRB::InputCompletor.retrieve_completion_data("1r.positi", bind: binding), "1r.positive?")
- assert_equal("Rational.positive?", IRB::InputCompletor.retrieve_completion_data("1r.positive?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("1r.positi", binding), "1r.positive?")
+ assert_equal("Rational.positive?", doc_namespace("1r.positive?", binding))
- assert_include(IRB::InputCompletor.retrieve_completion_data("0xFFFF.positi", bind: binding), "0xFFFF.positive?")
- assert_equal("Integer.positive?", IRB::InputCompletor.retrieve_completion_data("0xFFFF.positive?", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("0xFFFF.positi", binding), "0xFFFF.positive?")
+ assert_equal("Integer.positive?", doc_namespace("0xFFFF.positive?", binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("1i.positi", bind: binding))
+ assert_empty(completion_candidates("1i.positi", binding))
end
def test_complete_symbol
- assert_include(IRB::InputCompletor.retrieve_completion_data(":foo.to_p", bind: binding), ":foo.to_proc")
- assert_equal("Symbol.to_proc", IRB::InputCompletor.retrieve_completion_data(":foo.to_proc", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates(":foo.to_p", binding), ":foo.to_proc")
+ assert_equal("Symbol.to_proc", doc_namespace(":foo.to_proc", binding))
end
def test_complete_class
- assert_include(IRB::InputCompletor.retrieve_completion_data("String.ne", bind: binding), "String.new")
- assert_equal("String.new", IRB::InputCompletor.retrieve_completion_data("String.new", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("String.ne", binding), "String.new")
+ assert_equal("String.new", doc_namespace("String.new", binding))
end
end
class RequireComepletionTest < CompletionTest
def test_complete_require
- candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
%w['irb/init 'irb/ruby-lex].each do |word|
assert_include candidates, word
end
# Test cache
- candidates = IRB::InputCompletor::CompletionProc.("'irb", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
%w['irb/init 'irb/ruby-lex].each do |word|
assert_include candidates, word
end
@@ -84,7 +96,7 @@ module TestIRB
test_path = Pathname.new(temp_dir)
$LOAD_PATH << test_path
- candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
assert_include candidates, "'foo"
ensure
$LOAD_PATH.pop if test_path
@@ -98,7 +110,7 @@ module TestIRB
object.define_singleton_method(:to_s) { temp_dir }
$LOAD_PATH << object
- candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
assert_include candidates, "'foo"
ensure
$LOAD_PATH.pop if object
@@ -111,27 +123,27 @@ module TestIRB
$LOAD_PATH << object
assert_nothing_raised do
- IRB::InputCompletor::CompletionProc.("'foo", "require ", "")
+ IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
end
ensure
$LOAD_PATH.pop if object
end
def test_complete_require_library_name_first
- candidates = IRB::InputCompletor::CompletionProc.("'csv", "require ", "")
+ candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'csv", "", bind: binding)
assert_equal "'csv", candidates.first
end
def test_complete_require_relative
candidates = Dir.chdir(__dir__ + "/../..") do
- IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "")
+ IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding)
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 ", "")
+ IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding)
end
%w['lib/irb/init 'lib/irb/ruby-lex].each do |word|
assert_include candidates, word
@@ -160,13 +172,13 @@ module TestIRB
local_variables.clear
instance_variables.clear
- assert_include(IRB::InputCompletor.retrieve_completion_data("str_examp", bind: binding), "str_example")
- assert_equal("String", IRB::InputCompletor.retrieve_completion_data("str_example", bind: binding, doc_namespace: true))
- assert_equal("String.to_s", IRB::InputCompletor.retrieve_completion_data("str_example.to_s", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("str_examp", binding), "str_example")
+ assert_equal("String", doc_namespace("str_example", binding))
+ assert_equal("String.to_s", doc_namespace("str_example.to_s", binding))
- assert_include(IRB::InputCompletor.retrieve_completion_data("@str_examp", bind: binding), "@str_example")
- assert_equal("String", IRB::InputCompletor.retrieve_completion_data("@str_example", bind: binding, doc_namespace: true))
- assert_equal("String.to_s", IRB::InputCompletor.retrieve_completion_data("@str_example.to_s", bind: binding, doc_namespace: true))
+ assert_include(completion_candidates("@str_examp", binding), "@str_example")
+ assert_equal("String", doc_namespace("@str_example", binding))
+ assert_equal("String.to_s", doc_namespace("@str_example.to_s", binding))
end
def test_complete_sort_variables
@@ -176,7 +188,7 @@ module TestIRB
xzy_1.clear
xzy2.clear
- candidates = IRB::InputCompletor.retrieve_completion_data("xz", bind: binding, doc_namespace: false)
+ candidates = completion_candidates("xz", binding)
assert_equal(%w[xzy xzy2 xzy_1], candidates)
end
end
@@ -189,102 +201,12 @@ module TestIRB
end
def test_complete_constants
- assert_equal(["Foo"], IRB::InputCompletor.retrieve_completion_data("Fo", bind: binding))
- assert_equal(["Foo::B1", "Foo::B2", "Foo::B3"], IRB::InputCompletor.retrieve_completion_data("Foo::B", bind: binding))
- assert_equal(["Foo::B1.positive?"], IRB::InputCompletor.retrieve_completion_data("Foo::B1.pos", bind: binding))
-
- assert_equal(["::Forwardable"], IRB::InputCompletor.retrieve_completion_data("::Fo", bind: binding))
- assert_equal("Forwardable", IRB::InputCompletor.retrieve_completion_data("::Forwardable", bind: binding, doc_namespace: true))
- end
- end
-
- class PerfectMatchingTest < CompletionTest
- def setup
- # trigger PerfectMatchedProc to set up RDocRIDriver constant
- IRB::InputCompletor::PerfectMatchedProc.("foo", bind: binding)
-
- @original_use_stdout = IRB::InputCompletor::RDocRIDriver.use_stdout
- # force the driver to use stdout so it doesn't start a pager and interrupt tests
- IRB::InputCompletor::RDocRIDriver.use_stdout = true
- end
-
- def teardown
- IRB::InputCompletor::RDocRIDriver.use_stdout = @original_use_stdout
- end
-
- def test_perfectly_matched_namespace_triggers_document_display
- omit unless has_rdoc_content?
-
- out, err = capture_output do
- IRB::InputCompletor::PerfectMatchedProc.("String", bind: binding)
- end
-
- assert_empty(err)
-
- assert_include(out, " S\bSt\btr\bri\bin\bng\bg")
- end
-
- def test_perfectly_matched_multiple_namespaces_triggers_document_display
- result = nil
- out, err = capture_output do
- result = IRB::InputCompletor::PerfectMatchedProc.("{}.nil?", bind: binding)
- end
-
- assert_empty(err)
-
- # check if there're rdoc contents (e.g. CI doesn't generate them)
- if has_rdoc_content?
- # if there's rdoc content, we can verify by checking stdout
- # rdoc generates control characters for formatting method names
- assert_include(out, "P\bPr\bro\boc\bc.\b.n\bni\bil\bl?\b?") # Proc.nil?
- assert_include(out, "H\bHa\bas\bsh\bh.\b.n\bni\bil\bl?\b?") # Hash.nil?
- else
- # this is a hacky way to verify the rdoc rendering code path because CI doesn't have rdoc content
- # if there are multiple namespaces to be rendered, PerfectMatchedProc renders the result with a document
- # which always returns the bytes rendered, even if it's 0
- assert_equal(0, result)
- end
- end
-
- def test_not_matched_namespace_triggers_nothing
- result = nil
- out, err = capture_output do
- result = IRB::InputCompletor::PerfectMatchedProc.("Stri", bind: binding)
- end
-
- assert_empty(err)
- assert_empty(out)
- assert_nil(result)
- end
-
- def test_perfect_matching_stops_without_rdoc
- result = nil
-
- out, err = capture_output do
- without_rdoc do
- result = IRB::InputCompletor::PerfectMatchedProc.("String", bind: binding)
- end
- end
-
- assert_empty(err)
- assert_not_match(/from ruby core/, out)
- assert_nil(result)
- end
-
- def test_perfect_matching_handles_nil_namespace
- out, err = capture_output do
- # symbol literal has `nil` doc namespace so it's a good test subject
- assert_nil(IRB::InputCompletor::PerfectMatchedProc.(":aiueo", bind: binding))
- end
-
- assert_empty(err)
- assert_empty(out)
- end
-
- private
+ assert_equal(["Foo"], completion_candidates("Fo", binding))
+ assert_equal(["Foo::B1", "Foo::B2", "Foo::B3"], completion_candidates("Foo::B", binding))
+ assert_equal(["Foo::B1.positive?"], completion_candidates("Foo::B1.pos", binding))
- def has_rdoc_content?
- File.exist?(RDoc::RI::Paths::BASE)
+ assert_equal(["::Forwardable"], completion_candidates("::Fo", binding))
+ assert_equal("Forwardable", doc_namespace("::Forwardable", binding))
end
end
@@ -294,34 +216,34 @@ module TestIRB
rescue
end
symbols += [:aiueo, :"aiu eo"]
- candidates = IRB::InputCompletor.retrieve_completion_data(":a", bind: binding)
+ candidates = completion_candidates(":a", binding)
assert_include(candidates, ":aiueo")
assert_not_include(candidates, ":aiu eo")
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":irb_unknown_symbol_abcdefg", bind: binding))
+ assert_empty(completion_candidates(":irb_unknown_symbol_abcdefg", binding))
# Do not complete empty symbol for performance reason
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":", bind: binding))
+ assert_empty(completion_candidates(":", binding))
end
def test_complete_invalid_three_colons
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":::A", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data(":::", bind: binding))
+ assert_empty(completion_candidates(":::A", binding))
+ assert_empty(completion_candidates(":::", binding))
end
def test_complete_absolute_constants_with_special_characters
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A:", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A.", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A(", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A)", bind: binding))
- assert_empty(IRB::InputCompletor.retrieve_completion_data("::A[", bind: binding))
+ assert_empty(completion_candidates("::A:", binding))
+ assert_empty(completion_candidates("::A.", binding))
+ assert_empty(completion_candidates("::A(", binding))
+ assert_empty(completion_candidates("::A)", binding))
+ assert_empty(completion_candidates("::A[", binding))
end
def test_complete_reserved_words
- candidates = IRB::InputCompletor.retrieve_completion_data("de", bind: binding)
+ candidates = completion_candidates("de", binding)
%w[def defined?].each do |word|
assert_include candidates, word
end
- candidates = IRB::InputCompletor.retrieve_completion_data("__", bind: binding)
+ candidates = completion_candidates("__", binding)
%w[__ENCODING__ __LINE__ __FILE__].each do |word|
assert_include candidates, word
end
@@ -342,11 +264,11 @@ module TestIRB
}
bind = obj.instance_exec { binding }
- assert_include(IRB::InputCompletor.retrieve_completion_data("public_hog", bind: bind), "public_hoge")
- assert_include(IRB::InputCompletor.retrieve_completion_data("public_hoge", bind: bind, doc_namespace: true), "public_hoge")
+ assert_include(completion_candidates("public_hog", bind), "public_hoge")
+ assert_include(doc_namespace("public_hoge", bind), "public_hoge")
- assert_include(IRB::InputCompletor.retrieve_completion_data("private_hog", bind: bind), "private_hoge")
- assert_include(IRB::InputCompletor.retrieve_completion_data("private_hoge", bind: bind, doc_namespace: true), "private_hoge")
+ assert_include(completion_candidates("private_hog", bind), "private_hoge")
+ assert_include(doc_namespace("private_hoge", bind), "private_hoge")
end
end
end
diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb
index b516be5e3c..2d8cfadcf5 100644
--- a/test/irb/test_input_method.rb
+++ b/test/irb/test_input_method.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: false
require "irb"
-
+require "rdoc"
require_relative "helper"
module TestIRB
- class RelineInputMethodTest < TestCase
+ class InputMethodTest < TestCase
def setup
@conf_backup = IRB.conf.dup
IRB.conf[:LC_MESSAGES] = IRB::Locale.new
@@ -18,15 +18,19 @@ module TestIRB
# Reset Reline configuration overrided by RelineInputMethod.
Reline.instance_variable_set(:@core, nil)
end
+ end
+ class RelineInputMethodTest < InputMethodTest
def test_initialization
+ Reline.completion_proc = nil
+ Reline.dig_perfect_match_proc = nil
IRB::RelineInputMethod.new
assert_nil Reline.completion_append_character
assert_equal '', Reline.completer_quote_characters
- assert_equal IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS, Reline.basic_word_break_characters
- assert_equal IRB::InputCompletor::CompletionProc, Reline.completion_proc
- assert_equal IRB::InputCompletor::PerfectMatchedProc, Reline.dig_perfect_match_proc
+ assert_equal IRB::InputMethod::BASIC_WORD_BREAK_CHARACTERS, Reline.basic_word_break_characters
+ assert_not_nil Reline.completion_proc
+ assert_not_nil Reline.dig_perfect_match_proc
end
def test_initialization_without_use_autocomplete
@@ -54,7 +58,7 @@ module TestIRB
IRB::RelineInputMethod.new
assert Reline.autocompletion
- assert_equal IRB::RelineInputMethod::SHOW_DOC_DIALOG, Reline.dialog_proc(:show_doc).dialog_proc
+ assert_not_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc
ensure
Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT)
end
@@ -77,5 +81,93 @@ module TestIRB
Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT)
end
end
+
+ class DisplayDocumentTest < InputMethodTest
+ def setup
+ super
+ @driver = RDoc::RI::Driver.new(use_stdout: true)
+ end
+
+ def display_document(target, bind)
+ input_method = IRB::RelineInputMethod.new
+ input_method.instance_variable_set(:@completion_params, [target, '', '', bind])
+ input_method.display_document(target, driver: @driver)
+ end
+
+ def test_perfectly_matched_namespace_triggers_document_display
+ omit unless has_rdoc_content?
+
+ out, err = capture_output do
+ display_document("String", binding)
+ end
+
+ assert_empty(err)
+
+ assert_include(out, " S\bSt\btr\bri\bin\bng\bg")
+ end
+
+ def test_perfectly_matched_multiple_namespaces_triggers_document_display
+ result = nil
+ out, err = capture_output do
+ result = display_document("{}.nil?", binding)
+ end
+
+ assert_empty(err)
+
+ # check if there're rdoc contents (e.g. CI doesn't generate them)
+ if has_rdoc_content?
+ # if there's rdoc content, we can verify by checking stdout
+ # rdoc generates control characters for formatting method names
+ assert_include(out, "P\bPr\bro\boc\bc.\b.n\bni\bil\bl?\b?") # Proc.nil?
+ assert_include(out, "H\bHa\bas\bsh\bh.\b.n\bni\bil\bl?\b?") # Hash.nil?
+ else
+ # this is a hacky way to verify the rdoc rendering code path because CI doesn't have rdoc content
+ # if there are multiple namespaces to be rendered, PerfectMatchedProc renders the result with a document
+ # which always returns the bytes rendered, even if it's 0
+ assert_equal(0, result)
+ end
+ end
+
+ def test_not_matched_namespace_triggers_nothing
+ result = nil
+ out, err = capture_output do
+ result = display_document("Stri", binding)
+ end
+
+ assert_empty(err)
+ assert_empty(out)
+ assert_nil(result)
+ end
+
+ def test_perfect_matching_stops_without_rdoc
+ result = nil
+
+ out, err = capture_output do
+ without_rdoc do
+ result = display_document("String", binding)
+ end
+ end
+
+ assert_empty(err)
+ assert_not_match(/from ruby core/, out)
+ assert_nil(result)
+ end
+
+ def test_perfect_matching_handles_nil_namespace
+ out, err = capture_output do
+ # symbol literal has `nil` doc namespace so it's a good test subject
+ assert_nil(display_document(":aiueo", binding))
+ end
+
+ assert_empty(err)
+ assert_empty(out)
+ end
+
+ private
+
+ def has_rdoc_content?
+ File.exist?(RDoc::RI::Paths::BASE)
+ end
+ end
end