Skip to content

Commit

Permalink
Rename current completor to RegexpCompletor and refactored for future…
Browse files Browse the repository at this point in the history
… extension (#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
  • Loading branch information
tompng authored Oct 11, 2023
1 parent 202efdb commit 1e98521
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 337 deletions.
174 changes: 74 additions & 100 deletions lib/irb/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -73,7 +32,7 @@ def defined? do
[]
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)
Expand All @@ -84,8 +43,8 @@ def self.retrieve_gem_and_system_load_path
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|
Expand All @@ -103,13 +62,62 @@ def self.retrieve_files_to_require_from_load_path
)
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
Expand Down Expand Up @@ -142,21 +150,21 @@ def self.retrieve_files_to_require_relative_from_current_dir
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
Expand Down Expand Up @@ -394,44 +402,10 @@ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace
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_]/
Expand Down
Loading

0 comments on commit 1e98521

Please sign in to comment.