From fdb703a8de4ef3e7550e0f4bce244381c646a803 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 28 Dec 2012 15:19:21 -0800 Subject: [PATCH] Refactor completion API (git diff -w) --- lib/pry/commands/shell_mode.rb | 2 - lib/pry/completion.rb | 382 +++++++++++++++++---------------- lib/pry/pry_class.rb | 4 +- lib/pry/pry_instance.rb | 12 ++ lib/pry/repl.rb | 14 +- spec/completion_spec.rb | 20 +- 6 files changed, 218 insertions(+), 216 deletions(-) diff --git a/lib/pry/commands/shell_mode.rb b/lib/pry/commands/shell_mode.rb index 3e760431..f453e718 100644 --- a/lib/pry/commands/shell_mode.rb +++ b/lib/pry/commands/shell_mode.rb @@ -12,8 +12,6 @@ class Pry else _pry_.push_prompt Pry::SHELL_PROMPT _pry_.custom_completions = Pry::FILE_COMPLETIONS - Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, - _pry_.instance_eval(&Pry::FILE_COMPLETIONS) end end end diff --git a/lib/pry/completion.rb b/lib/pry/completion.rb index 1e681c8d..a197bab5 100644 --- a/lib/pry/completion.rb +++ b/lib/pry/completion.rb @@ -3,37 +3,38 @@ class Pry module BondCompleter - - def self.build_completion_proc(target, pry=nil, commands=[""]) - if !@started - @started = true - start - end - - Pry.th[:pry] = pry - proc{ |*a| Bond.agent.call(*a) } + def self.call(input, options) + Pry.th[:pry] = options[:pry] + Bond.agent.call(input) end def self.start - Bond.start(:eval_binding => lambda{ Pry.th[:pry].current_context }) + Bond.start(:eval_binding => lambda{ Pry.th[:pry] && Pry.th[:pry].current_context || TOPLEVEL_BINDING }) Bond.complete(:on => /\A/) do |input| Pry.commands.complete(input.line, :pry_instance => Pry.th[:pry], :target => Pry.th[:pry].current_context, :command_set => Pry.th[:pry].commands) end + self end - end # Implements tab completion for Readline in Pry module InputCompleter - if Readline.respond_to?("basic_word_break_characters=") - Readline.basic_word_break_characters = " \t\n\"\\'`><=;|&{(" + def self.call(input, options) + build_completion_proc(options[:target], options[:pry], options[:custom_completions]).call input end - Readline.completion_append_character = nil + def self.start + if Readline.respond_to?("basic_word_break_characters=") + Readline.basic_word_break_characters = " \t\n\"\\'`><=;|&{(" + end + + Readline.completion_append_character = nil + self + end ReservedWords = [ "BEGIN", "END", @@ -63,199 +64,200 @@ class Pry # Return a new completion proc for use by Readline. # @param [Binding] target The current binding context. # @param [Array] commands The array of Pry commands. - def self.build_completion_proc(target, pry=nil, commands=[""]) + def self.call(input, options) - proc do |input| + custom_completions = options[:custom_completions] || [] - # if there are multiple contexts e.g. cd 1/2/3 - # get new target for 1/2 and find candidates for 3 - path, input = build_path(input) + # if there are multiple contexts e.g. cd 1/2/3 + # get new target for 1/2 and find candidates for 3 + path, input = build_path(input) - unless path.call.empty? - target, _ = Pry::Helpers::BaseHelpers.context_from_object_path(path.call, pry) - target = target.last - end - - begin - bind = target + if path.call.empty? + target = options[:target] + else + target, _ = Pry::Helpers::BaseHelpers.context_from_object_path(path.call, options[:pry]) + target = target.last + end - case input + begin + bind = target + + case input - # Complete stdlib symbols + # Complete stdlib symbols - when /^(\/[^\/]*\/)\.([^.]*)$/ - # Regexp - receiver = $1 - message = Regexp.quote($2) + when /^(\/[^\/]*\/)\.([^.]*)$/ + # Regexp + receiver = $1 + message = Regexp.quote($2) - candidates = Regexp.instance_methods.collect(&:to_s) - select_message(path, receiver, message, candidates) + candidates = Regexp.instance_methods.collect(&:to_s) + select_message(path, receiver, message, candidates) - when /^([^\]]*\])\.([^.]*)$/ - # Array - receiver = $1 - message = Regexp.quote($2) + when /^([^\]]*\])\.([^.]*)$/ + # Array + receiver = $1 + message = Regexp.quote($2) - candidates = Array.instance_methods.collect(&:to_s) - select_message(path, receiver, message, candidates) + candidates = Array.instance_methods.collect(&:to_s) + select_message(path, receiver, message, candidates) - when /^([^\}]*\})\.([^.]*)$/ - # Proc or Hash - receiver = $1 - message = Regexp.quote($2) + when /^([^\}]*\})\.([^.]*)$/ + # Proc or Hash + receiver = $1 + message = Regexp.quote($2) - candidates = Proc.instance_methods.collect(&:to_s) - candidates |= Hash.instance_methods.collect(&:to_s) - select_message(path, receiver, message, candidates) + candidates = Proc.instance_methods.collect(&:to_s) + candidates |= Hash.instance_methods.collect(&:to_s) + select_message(path, receiver, message, candidates) - when /^(:[^:.]*)$/ - # Symbol - if Symbol.respond_to?(:all_symbols) - sym = Regexp.quote($1) - candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name} - - candidates.grep(/^#{sym}/) - else - [] - end - - when /^::([A-Z][^:\.\(]*)$/ - # Absolute Constant or class methods - receiver = $1 - candidates = Object.constants.collect(&:to_s) - candidates.grep(/^#{receiver}/).collect{|e| "::" + e} - - - # Complete target symbols - - when /^([A-Z][A-Za-z0-9]*)$/ - # Constant - message = $1 - - begin - context = target.eval("self") - context = context.class unless context.respond_to? :constants - candidates = context.constants.collect(&:to_s) - rescue - candidates = [] - end - candidates = candidates.grep(/^#{message}/).collect(&path) - - when /^([A-Z].*)::([^:.]*)$/ - # Constant or class methods - receiver = $1 - message = Regexp.quote($2) - begin - candidates = eval("#{receiver}.constants.collect(&:to_s)", bind) - candidates |= eval("#{receiver}.methods.collect(&:to_s)", bind) - rescue RescuableException - candidates = [] - end - candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e} - - when /^(:[^:.]+)\.([^.]*)$/ - # Symbol - receiver = $1 - message = Regexp.quote($2) - - candidates = Symbol.instance_methods.collect(&:to_s) - select_message(path, receiver, message, candidates) - - when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/ - # Numeric - receiver = $1 - message = Regexp.quote($5) - - begin - candidates = eval(receiver, bind).methods.collect(&:to_s) - rescue RescuableException - candidates = [] - end - select_message(path, receiver, message, candidates) - - when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/# - # Numeric(0xFFFF) - receiver = $1 - message = Regexp.quote($2) - - begin - candidates = eval(receiver, bind).methods.collect(&:to_s) - rescue RescuableException - candidates = [] - end - select_message(path, receiver, message, candidates) - - when /^(\$[^.]*)$/ - # Global variables - regmessage = Regexp.new(Regexp.quote($1)) - candidates = global_variables.collect(&:to_s).grep(regmessage) - - when /^([^."].*)\.([^.]*)$/ - # Variable - receiver = $1 - message = Regexp.quote($2) - - gv = eval("global_variables", bind).collect(&:to_s) - lv = eval("local_variables", bind).collect(&:to_s) - cv = eval("self.class.constants", bind).collect(&:to_s) - - if (gv | lv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver - # foo.func and foo is local var. OR - # Foo::Bar.func - begin - candidates = eval("#{receiver}.methods", bind).collect(&:to_s) - rescue RescuableException - candidates = [] - end - else - # func1.func2 - candidates = [] - ObjectSpace.each_object(Module){|m| - begin - name = m.name.to_s - rescue RescuableException - name = "" - end - next if name != "IRB::Context" and - /^(IRB|SLex|RubyLex|RubyToken)/ =~ name - - # jruby doesn't always provide #instance_methods() on each - # object. - if m.respond_to?(:instance_methods) - candidates.concat m.instance_methods(false).collect(&:to_s) - end - } - candidates.sort! - candidates.uniq! - end - select_message(path, receiver, message, candidates) - - when /^\.([^.]*)$/ - # Unknown(maybe String) - receiver = "" - message = Regexp.quote($1) - - candidates = String.instance_methods(true).collect(&:to_s) - select_message(path, receiver, message, candidates) + when /^(:[^:.]*)$/ + # Symbol + if Symbol.respond_to?(:all_symbols) + sym = Regexp.quote($1) + candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name} + candidates.grep(/^#{sym}/) else - - candidates = eval( - "methods | private_methods | local_variables | " \ - "self.class.constants | instance_variables", - bind - ).collect(&:to_s) - - if eval("respond_to?(:class_variables)", bind) - candidates += eval("class_variables", bind).collect(&:to_s) - end - candidates = (candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/) - candidates.collect(&path) + [] end - rescue RescuableException - [] + + when /^::([A-Z][^:\.\(]*)$/ + # Absolute Constant or class methods + receiver = $1 + candidates = Object.constants.collect(&:to_s) + candidates.grep(/^#{receiver}/).collect{|e| "::" + e} + + + # Complete target symbols + + when /^([A-Z][A-Za-z0-9]*)$/ + # Constant + message = $1 + + begin + context = target.eval("self") + context = context.class unless context.respond_to? :constants + candidates = context.constants.collect(&:to_s) + rescue + candidates = [] + end + candidates = candidates.grep(/^#{message}/).collect(&path) + + when /^([A-Z].*)::([^:.]*)$/ + # Constant or class methods + receiver = $1 + message = Regexp.quote($2) + begin + candidates = eval("#{receiver}.constants.collect(&:to_s)", bind) + candidates |= eval("#{receiver}.methods.collect(&:to_s)", bind) + rescue RescuableException + candidates = [] + end + candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e} + + when /^(:[^:.]+)\.([^.]*)$/ + # Symbol + receiver = $1 + message = Regexp.quote($2) + + candidates = Symbol.instance_methods.collect(&:to_s) + select_message(path, receiver, message, candidates) + + when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/ + # Numeric + receiver = $1 + message = Regexp.quote($5) + + begin + candidates = eval(receiver, bind).methods.collect(&:to_s) + rescue RescuableException + candidates = [] + end + select_message(path, receiver, message, candidates) + + when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/# + # Numeric(0xFFFF) + receiver = $1 + message = Regexp.quote($2) + + begin + candidates = eval(receiver, bind).methods.collect(&:to_s) + rescue RescuableException + candidates = [] + end + select_message(path, receiver, message, candidates) + + when /^(\$[^.]*)$/ + # Global variables + regmessage = Regexp.new(Regexp.quote($1)) + candidates = global_variables.collect(&:to_s).grep(regmessage) + + when /^([^."].*)\.([^.]*)$/ + # Variable + receiver = $1 + message = Regexp.quote($2) + + gv = eval("global_variables", bind).collect(&:to_s) + lv = eval("local_variables", bind).collect(&:to_s) + cv = eval("self.class.constants", bind).collect(&:to_s) + + if (gv | lv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver + # foo.func and foo is local var. OR + # Foo::Bar.func + begin + candidates = eval("#{receiver}.methods", bind).collect(&:to_s) + rescue RescuableException + candidates = [] + end + else + # func1.func2 + candidates = [] + ObjectSpace.each_object(Module){|m| + begin + name = m.name.to_s + rescue RescuableException + name = "" + end + next if name != "IRB::Context" and + /^(IRB|SLex|RubyLex|RubyToken)/ =~ name + + # jruby doesn't always provide #instance_methods() on each + # object. + if m.respond_to?(:instance_methods) + candidates.concat m.instance_methods(false).collect(&:to_s) + end + } + candidates.sort! + candidates.uniq! + end + select_message(path, receiver, message, candidates) + + when /^\.([^.]*)$/ + # Unknown(maybe String) + receiver = "" + message = Regexp.quote($1) + + candidates = String.instance_methods(true).collect(&:to_s) + select_message(path, receiver, message, candidates) + + else + + candidates = eval( + "methods | private_methods | local_variables | " \ + "self.class.constants | instance_variables", + bind + ).collect(&:to_s) + + if eval("respond_to?(:class_variables)", bind) + candidates += eval("class_variables", bind).collect(&:to_s) + end + candidates = (candidates|ReservedWords|custom_completions).grep(/^#{Regexp.quote(input)}/) + candidates.collect(&path) end + rescue RescuableException + [] end end diff --git a/lib/pry/pry_class.rb b/lib/pry/pry_class.rb index 89d77510..c399c78a 100644 --- a/lib/pry/pry_class.rb +++ b/lib/pry/pry_class.rb @@ -270,9 +270,9 @@ class Pry config.output_prefix = "=> " if defined?(Bond) && Readline::VERSION !~ /editline/i - config.completer = Pry::BondCompleter + config.completer = Pry::BondCompleter.start else - config.completer = Pry::InputCompleter + config.completer = Pry::InputCompleter.start end config.gist ||= OpenStruct.new diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index fb4dabfb..ab4f2895 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -171,6 +171,18 @@ class Pry end end + # Generate completions. + # + # @param [String] what the user has typed so far + # @return [Array] possible completions + def complete(input) + Pry.critical_section do + Pry.config.completer.call(input, :target => current_binding, + :pry => self, + :custom_completions => instance_eval(&custom_completions)) + end + end + # Injects a local variable into the provided binding. # @param [String] name The name of the local to inject. # @param [Object] value The value to set the local to. diff --git a/lib/pry/repl.rb b/lib/pry/repl.rb index de2e787e..90faacd3 100644 --- a/lib/pry/repl.rb +++ b/lib/pry/repl.rb @@ -70,15 +70,11 @@ class Pry @indent.reset if pry.eval_string.empty? current_prompt = pry.select_prompt - completion_proc = Pry.config.completer.build_completion_proc(pry.current_binding, pry, - pry.instance_eval(&pry.custom_completions)) - - safe_completion_proc = proc{ |*a| Pry.critical_section{ completion_proc.call(*a) } } indentation = Pry.config.auto_indent ? @indent.current_prefix : '' begin - val = read_line("#{current_prompt}#{indentation}", safe_completion_proc) + val = read_line("#{current_prompt}#{indentation}") # Handle like Bash, empty the current input buffer but do not quit. # This is only for ruby-1.9; other versions of ruby do not let you send Interrupt @@ -148,15 +144,17 @@ class Pry # Returns the next line of input to be used by the pry instance. # @param [String] current_prompt The prompt to use for input. # @return [String] The next line of input. - def read_line(current_prompt="> ", completion_proc=nil) + def read_line(current_prompt) handle_read_errors do if defined? Coolline and input.is_a? Coolline input.completion_proc = proc do |cool| - completions = completion_proc.call cool.completed_word + completions = @pry.complete cool.completed_word completions.compact end elsif input.respond_to? :completion_proc= - input.completion_proc = completion_proc + input.completion_proc = proc do |input| + @pry.complete input + end end if input == Readline diff --git a/spec/completion_spec.rb b/spec/completion_spec.rb index bdccecf2..18dde6f7 100644 --- a/spec/completion_spec.rb +++ b/spec/completion_spec.rb @@ -1,12 +1,8 @@ require 'helper' -def new_completer(bind, pry=nil) - Pry::InputCompleter.build_completion_proc(Pry.binding_for(bind), pry) -end - def completer_test(bind, pry=nil, assert_flag=true) - completer = new_completer(bind, pry) - test = proc {|symbol| completer.call(symbol[0..-2]).include?(symbol).should == assert_flag} + test = proc {|symbol| + Pry::InputCompleter.call(symbol[0..-2], :target => Pry.binding_for(bind), :pry => pry).include?(symbol).should == assert_flag} return proc {|*symbols| symbols.each(&test) } end @@ -40,16 +36,12 @@ describe Pry::InputCompleter do # another jruby hack :(( if !Pry::Helpers::BaseHelpers.jruby? it "should not crash if there's a Module that has a symbolic name." do - completer = Pry::InputCompleter.build_completion_proc(Pry.binding_for(Object.new)) - lambda{ completer.call "a.to_s." }.should.not.raise Exception + lambda{ Pry::InputCompleter.call "a.to_s.", :target => Pry.binding_for(Object.new) }.should.not.raise Exception end end it 'should take parenthesis and other characters into account for symbols' do - b = Pry.binding_for(Object.new) - completer = Pry::InputCompleter.build_completion_proc(b) - - lambda { completer.call(":class)") }.should.not.raise(RegexpError) + lambda { Pry::InputCompleter.call(":class)", :target => Pry.binding_for(Object.new)) }.should.not.raise(RegexpError) end it 'should complete instance variables' do @@ -128,7 +120,7 @@ describe Pry::InputCompleter do completer_test(binding).call('o.foo') # trailing slash - new_completer(Mod).call('Mod2/').include?('Mod2/').should == true + Pry::InputCompleter.call('Mod2/', :target => Pry.binding_for(Mod)).include?('Mod2/').should == true end it 'should complete for arbitrary scopes' do @@ -199,7 +191,7 @@ describe Pry::InputCompleter do completer_test(binding).call('o.foo') # trailing slash - new_completer(Mod).call('Mod2/').include?('Mod2/').should == true + Pry::InputCompleter.call('Mod2/', :target => Pry.binding_for(Mod)).include?('Mod2/').should == true end