From 1c61178b4c06d0dcd50b5cf9a8791ff7fd0a90db Mon Sep 17 00:00:00 2001 From: aycabta Date: Wed, 24 Mar 2021 14:55:05 +0900 Subject: [PATCH] Complete require and require_relative --- lib/irb/completion.rb | 58 +++++++++++++++++++++++++++++++++++-- lib/irb/input-method.rb | 1 + test/irb/test_completion.rb | 14 +++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 22a1ad1d3..0385142a3 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -9,6 +9,8 @@ autoload :RDoc, "rdoc" +require_relative 'ruby-lex' + module IRB module InputCompletor # :nodoc: @@ -38,8 +40,60 @@ def defined? do BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{(" - CompletionProc = proc { |input| - retrieve_completion_data(input).compact.map{ |i| i.encode(Encoding.default_external) } + CompletionRequireProc = lambda { |target, preposing = nil, postposing = nil| + if target =~ /\A(['"])([^'"]+)\Z/ + quote = $1 + actual_target = $2 + else + return nil # It's not String literal + end + tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, '')) + tok = nil + tokens.reverse_each do |t| + unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event) + tok = t + break + end + end + if tok && tok.event == :on_ident && tok.state == Ripper::EXPR_CMDARG + case tok.tok + when 'require' + result = $LOAD_PATH.flat_map { |path| + begin + Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path) + rescue Errno::ENOENT + [] + end + }.uniq.map { |path| + path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') + }.select { |path| + path.start_with?(actual_target) + }.map { |path| + quote + path + } + when 'require_relative' + result = Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| + path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') + }.select { |path| + path.start_with?(actual_target) + }.map { |path| + quote + path + } + end + end + result + } + + CompletionProc = lambda { |target, preposing = nil, postposing = nil| + if preposing && postposing + result = CompletionRequireProc.(target, preposing, postposing) + unless result + result = retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) } + end + result + else + retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) } + end } def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding, doc_namespace: false) diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index e22367298..1854567a2 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -280,6 +280,7 @@ def initialize Reline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS end Reline.completion_append_character = nil + Reline.completer_quote_characters = '' Reline.completion_proc = IRB::InputCompletor::CompletionProc Reline.output_modifier_proc = if IRB.conf[:USE_COLORIZE] diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 984453d05..de043c708 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -55,5 +55,19 @@ def test_complete_predicate? 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 + end + + def test_complete_require_relative + candidates = IRB::InputCompletor::CompletionProc.("'lib/irb", "require_relative ", "") + %w['lib/irb/init 'lib/irb/ruby-lex].each do |word| + assert_include candidates, word + end + end end end