[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
This commit is contained in:
parent
b9a6fca67d
commit
94cb5765e2
@ -8,7 +8,68 @@
|
||||
require_relative 'ruby-lex'
|
||||
|
||||
module IRB
|
||||
module InputCompletor # :nodoc:
|
||||
class BaseCompletor # :nodoc:
|
||||
def completion_candidates(preposing, target, postposing, bind:)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def doc_namespace(preposing, matched, postposing, bind:)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
GEM_PATHS =
|
||||
if defined?(Gem::Specification)
|
||||
Gem::Specification.latest_specs(true).map { |s|
|
||||
s.require_paths.map { |p|
|
||||
if File.absolute_path?(p)
|
||||
p
|
||||
else
|
||||
File.join(s.full_gem_path, p)
|
||||
end
|
||||
}
|
||||
}.flatten
|
||||
else
|
||||
[]
|
||||
end.freeze
|
||||
|
||||
def retrieve_gem_and_system_load_path
|
||||
candidates = (GEM_PATHS | $LOAD_PATH)
|
||||
candidates.map do |p|
|
||||
if p.respond_to?(:to_path)
|
||||
p.to_path
|
||||
else
|
||||
String(p) rescue nil
|
||||
end
|
||||
end.compact.sort
|
||||
end
|
||||
|
||||
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|
|
||||
begin
|
||||
names = Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path)
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
next if names.empty?
|
||||
names.map! { |n| n.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') }.sort!
|
||||
shortest << names.shift
|
||||
result.concat(names)
|
||||
}
|
||||
shortest.sort! | rest
|
||||
)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
class RegexpCompletor < BaseCompletor # :nodoc:
|
||||
using Module.new {
|
||||
refine ::Binding do
|
||||
def eval_methods
|
||||
@ -56,60 +117,7 @@ module IRB
|
||||
yield
|
||||
]
|
||||
|
||||
BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{("
|
||||
|
||||
GEM_PATHS =
|
||||
if defined?(Gem::Specification)
|
||||
Gem::Specification.latest_specs(true).map { |s|
|
||||
s.require_paths.map { |p|
|
||||
if File.absolute_path?(p)
|
||||
p
|
||||
else
|
||||
File.join(s.full_gem_path, p)
|
||||
end
|
||||
}
|
||||
}.flatten
|
||||
else
|
||||
[]
|
||||
end.freeze
|
||||
|
||||
def self.retrieve_gem_and_system_load_path
|
||||
candidates = (GEM_PATHS | $LOAD_PATH)
|
||||
candidates.map do |p|
|
||||
if p.respond_to?(:to_path)
|
||||
p.to_path
|
||||
else
|
||||
String(p) rescue nil
|
||||
end
|
||||
end.compact.sort
|
||||
end
|
||||
|
||||
def self.retrieve_files_to_require_from_load_path
|
||||
@@files_from_load_path ||=
|
||||
(
|
||||
shortest = []
|
||||
rest = retrieve_gem_and_system_load_path.each_with_object([]) { |path, result|
|
||||
begin
|
||||
names = Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path)
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
next if names.empty?
|
||||
names.map! { |n| n.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') }.sort!
|
||||
shortest << names.shift
|
||||
result.concat(names)
|
||||
}
|
||||
shortest.sort! | rest
|
||||
)
|
||||
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|
|
||||
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_]/
|
||||
|
@ -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
|
||||
name = result[pointer]
|
||||
name = IRB::InputCompletor.retrieve_completion_data(name, doc_namespace: true)
|
||||
|
||||
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 just_cursor_moving and completion_journey_data.nil?
|
||||
return nil
|
||||
end
|
||||
end
|
||||
cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4)
|
||||
return nil if result.nil? or pointer.nil? or pointer < 0
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
if key.match?(dialog.name)
|
||||
begin
|
||||
driver.display_names([name])
|
||||
rescue RDoc::RI::Driver::NotFoundError
|
||||
end
|
||||
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
|
||||
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
|
||||
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
|
||||
else
|
||||
x = right_x
|
||||
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)
|
||||
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')
|
||||
}
|
||||
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
|
||||
begin
|
||||
driver.display_names([namespace])
|
||||
rescue RDoc::RI::Driver::NotFoundError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Reads the next line from this input method.
|
||||
#
|
||||
|
@ -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(["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))
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user