diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 35767feeb..dbd652769 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -171,10 +171,10 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace receiver = $1 message = $3 - candidates = String.instance_methods.collect{|m| m.to_s} if doc_namespace "String.#{message}" else + candidates = String.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates) end @@ -183,10 +183,10 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace receiver = $1 message = $2 - candidates = Regexp.instance_methods.collect{|m| m.to_s} if doc_namespace "Regexp.#{message}" else + candidates = Regexp.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates) end @@ -195,10 +195,10 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace receiver = $1 message = $2 - candidates = Array.instance_methods.collect{|m| m.to_s} if doc_namespace "Array.#{message}" else + candidates = Array.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates) end @@ -207,29 +207,33 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace receiver = $1 message = $2 - proc_candidates = Proc.instance_methods.collect{|m| m.to_s} - hash_candidates = Hash.instance_methods.collect{|m| m.to_s} if doc_namespace ["Proc.#{message}", "Hash.#{message}"] else + proc_candidates = Proc.instance_methods.collect{|m| m.to_s} + hash_candidates = Hash.instance_methods.collect{|m| m.to_s} select_message(receiver, message, proc_candidates | hash_candidates) end when /^(:[^:.]*)$/ # Symbol - return nil if doc_namespace - sym = $1 - candidates = Symbol.all_symbols.collect do |s| - ":" + s.id2name.encode(Encoding.default_external) - rescue EncodingError - # ignore + if doc_namespace + nil + else + sym = $1 + candidates = Symbol.all_symbols.collect do |s| + ":" + s.id2name.encode(Encoding.default_external) + rescue EncodingError + # ignore + end + candidates.grep(/^#{Regexp.quote(sym)}/) end - candidates.grep(/^#{Regexp.quote(sym)}/) - when /^::([A-Z][^:\.\(\)]*)$/ # Absolute Constant or class methods receiver = $1 + candidates = Object.constants.collect{|m| m.to_s} + if doc_namespace candidates.find { |i| i == receiver } else @@ -240,16 +244,18 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace # Constant or class methods receiver = $1 message = $2 - begin - candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) - candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) - rescue Exception - candidates = [] - end + if doc_namespace "#{receiver}::#{message}" else - select_message(receiver, message, candidates, "::") + begin + candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) + candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) + rescue Exception + candidates = [] + end + + select_message(receiver, message, candidates.sort, "::") end when /^(:[^:.]+)(\.|::)([^.]*)$/ @@ -258,10 +264,10 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace sep = $2 message = $3 - candidates = Symbol.instance_methods.collect{|m| m.to_s} if doc_namespace "Symbol.#{message}" else + candidates = Symbol.instance_methods.collect{|m| m.to_s} select_message(receiver, message, candidates, sep) end @@ -273,6 +279,7 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace begin instance = eval(receiver, bind) + if doc_namespace "#{instance.class.name}.#{message}" else @@ -283,7 +290,7 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace if doc_namespace nil else - candidates = [] + [] end end @@ -305,7 +312,7 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace if doc_namespace nil else - candidates = [] + [] end end @@ -313,6 +320,7 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace # global var gvar = $1 all_gvars = global_variables.collect{|m| m.to_s} + if doc_namespace all_gvars.find{ |i| i == gvar } else @@ -356,6 +364,7 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace candidates.sort! candidates.uniq! end + if doc_namespace rec_class = rec.is_a?(Module) ? rec : rec.class "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" @@ -370,6 +379,7 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace message = $1 candidates = String.instance_methods(true).collect{|m| m.to_s} + if doc_namespace "String.#{candidates.find{ |i| i == message }}" else diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 8d7476b8f..df5a78c69 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -27,9 +27,188 @@ def test_nonstring_module_name end end - def test_complete_numeric - assert_include(IRB::InputCompletor.retrieve_completion_data("1r.positi", bind: binding), "1r.positive?") - assert_empty(IRB::InputCompletor.retrieve_completion_data("1i.positi", bind: binding)) + class TestMethodCompletion < TestCompletion + def test_complete_string + assert_include(IRB::InputCompletor.retrieve_completion_data("'foo'.up", bind: binding), "'foo'.upcase") + assert_equal("String.upcase", IRB::InputCompletor.retrieve_completion_data("'foo'.upcase", bind: binding, doc_namespace: true)) + end + + def test_complete_regexp + assert_include(IRB::InputCompletor.retrieve_completion_data("/foo/.ma", bind: binding), "/foo/.match") + assert_equal("Regexp.match", IRB::InputCompletor.retrieve_completion_data("/foo/.match", bind: binding, doc_namespace: true)) + 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)) + 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)) + + # 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)) + 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(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(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_empty(IRB::InputCompletor.retrieve_completion_data("1i.positi", bind: 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)) + 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)) + end + end + + class TestRequireComepletion < TestCompletion + 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_with_pathname_in_load_path + temp_dir = Dir.mktmpdir + File.write(File.join(temp_dir, "foo.rb"), "test") + test_path = Pathname.new(temp_dir) + $LOAD_PATH << test_path + + candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "") + assert_include candidates, "'foo" + ensure + $LOAD_PATH.pop if test_path + FileUtils.remove_entry(temp_dir) if temp_dir + end + + def test_complete_require_with_string_convertable_in_load_path + temp_dir = Dir.mktmpdir + File.write(File.join(temp_dir, "foo.rb"), "test") + object = Object.new + object.define_singleton_method(:to_s) { temp_dir } + $LOAD_PATH << object + + candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "") + assert_include candidates, "'foo" + ensure + $LOAD_PATH.pop if object + FileUtils.remove_entry(temp_dir) if temp_dir + end + + def test_complete_require_with_malformed_object_in_load_path + object = Object.new + def object.to_s; raise; end + $LOAD_PATH << object + + assert_nothing_raised do + IRB::InputCompletor::CompletionProc.("'foo", "require ", "") + end + ensure + $LOAD_PATH.pop if object + end + + def test_complete_require_library_name_first + pend 'Need to use virtual library paths' + candidates = IRB::InputCompletor::CompletionProc.("'csv", "require ", "") + assert_equal "'csv", candidates.first + 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 + + class TestVariableCompletion < TestCompletion + def test_complete_variable + # Bug fix issues https://github.com/ruby/irb/issues/368 + # Variables other than `str_example` and `@str_example` are defined to ensure that irb completion does not cause unintended behavior + str_example = '' + @str_example = '' + private_methods = '' + methods = '' + global_variables = '' + local_variables = '' + instance_variables = '' + + # suppress "assigned but unused variable" warning + str_example.clear + @str_example.clear + private_methods.clear + methods.clear + global_variables.clear + 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(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)) + end + + def test_complete_sort_variables + xzy, xzy_1, xzy2 = '', '', '' + + xzy.clear + xzy_1.clear + xzy2.clear + + candidates = IRB::InputCompletor.retrieve_completion_data("xz", bind: binding, doc_namespace: false) + assert_equal(%w[xzy xzy2 xzy_1], candidates) + end + end + + class TestConstantCompletion < TestCompletion + class Foo + B3 = 1 + B1 = 1 + B2 = 1 + 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 def test_complete_symbol @@ -70,116 +249,6 @@ def test_complete_reserved_words end end - def test_complete_predicate? - candidates = IRB::InputCompletor.retrieve_completion_data("1.posi", bind: binding) - assert_include candidates, '1.positive?' - - 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_with_pathname_in_load_path - temp_dir = Dir.mktmpdir - File.write(File.join(temp_dir, "foo.rb"), "test") - test_path = Pathname.new(temp_dir) - $LOAD_PATH << test_path - - candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "") - assert_include candidates, "'foo" - ensure - $LOAD_PATH.pop if test_path - FileUtils.remove_entry(temp_dir) if temp_dir - end - - def test_complete_require_with_string_convertable_in_load_path - temp_dir = Dir.mktmpdir - File.write(File.join(temp_dir, "foo.rb"), "test") - object = Object.new - object.define_singleton_method(:to_s) { temp_dir } - $LOAD_PATH << object - - candidates = IRB::InputCompletor::CompletionProc.("'foo", "require ", "") - assert_include candidates, "'foo" - ensure - $LOAD_PATH.pop if object - FileUtils.remove_entry(temp_dir) if temp_dir - end - - def test_complete_require_with_malformed_object_in_load_path - object = Object.new - def object.to_s; raise; end - $LOAD_PATH << object - - assert_nothing_raised do - IRB::InputCompletor::CompletionProc.("'foo", "require ", "") - end - ensure - $LOAD_PATH.pop if object - end - - def test_complete_require_library_name_first - pend 'Need to use virtual library paths' - candidates = IRB::InputCompletor::CompletionProc.("'csv", "require ", "") - assert_equal "'csv", candidates.first - 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 - - def test_complete_variable - # Bug fix issues https://github.com/ruby/irb/issues/368 - # Variables other than `str_example` and `@str_example` are defined to ensure that irb completion does not cause unintended behavior - str_example = '' - @str_example = '' - private_methods = '' - methods = '' - global_variables = '' - local_variables = '' - instance_variables = '' - - # suppress "assigned but unused variable" warning - str_example.clear - @str_example.clear - private_methods.clear - methods.clear - global_variables.clear - local_variables.clear - instance_variables.clear - - assert_include(IRB::InputCompletor.retrieve_completion_data("str_examp", bind: binding), "str_example") - assert_equal(IRB::InputCompletor.retrieve_completion_data("str_example", bind: binding, doc_namespace: true), "String") - assert_equal(IRB::InputCompletor.retrieve_completion_data("str_example.to_s", bind: binding, doc_namespace: true), "String.to_s") - - assert_include(IRB::InputCompletor.retrieve_completion_data("@str_examp", bind: binding), "@str_example") - assert_equal(IRB::InputCompletor.retrieve_completion_data("@str_example", bind: binding, doc_namespace: true), "String") - assert_equal(IRB::InputCompletor.retrieve_completion_data("@str_example.to_s", bind: binding, doc_namespace: true), "String.to_s") - end - def test_complete_methods obj = Object.new obj.singleton_class.class_eval { @@ -203,21 +272,5 @@ def instance_variables; end assert_include(IRB::InputCompletor.retrieve_completion_data("private_hoge.to_s", bind: bind), "private_hoge.to_s") assert_include(IRB::InputCompletor.retrieve_completion_data("private_hoge", bind: bind, doc_namespace: true), "private_hoge") end - - def test_complete_class_method - assert_include(IRB::InputCompletor.retrieve_completion_data("String.new", bind: binding), "String.new") - assert_equal(IRB::InputCompletor.retrieve_completion_data("String.new", bind: binding, doc_namespace: true), "String.new") - end - - def test_complete_sort_variables - xzy, xzy_1, xzy2 = '', '', '' - - xzy.clear - xzy_1.clear - xzy2.clear - - candidates = IRB::InputCompletor.retrieve_completion_data("xz", bind: binding, doc_namespace: false) - assert_equal(candidates, %w[xzy xzy2 xzy_1]) - end end end