From b0d45a056f23b36bd601b1e968e44aaae6ef2c67 Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Fri, 3 Aug 2012 21:20:20 -0400 Subject: [PATCH 1/7] add support for tabbing into previous scope --- lib/pry/completion.rb | 34 +++++++++++++++----- lib/pry/default_commands/input_and_output.rb | 2 +- lib/pry/pry_instance.rb | 2 +- test/test_completion.rb | 22 +++++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/lib/pry/completion.rb b/lib/pry/completion.rb index 9d7c913e..e64e1463 100644 --- a/lib/pry/completion.rb +++ b/lib/pry/completion.rb @@ -41,7 +41,7 @@ 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, commands=[""]) + def self.build_completion_proc(target, pry=nil, commands=[""]) proc do |input| begin bind = target @@ -177,6 +177,20 @@ class Pry end select_message(receiver, message, candidates) + when /^(\.{2}\/)(.*)$/ + # previous scope (../) * + stack = pry.binding_stack.dup + stack.pop + if stack.length >= 1 + receiver = $1 + message = $2 + scope = stack[-1] + + ivars = eval("instance_variables", scope) + locals = eval("local_variables", scope) + select_message(receiver, message, ivars + locals) + end + when /^\.([^.]*)$/ # unknown(maybe String) @@ -207,13 +221,17 @@ class Pry def self.select_message(receiver, message, candidates) candidates.grep(/^#{message}/).collect do |e| - case e - when /^[a-zA-Z_]/ - receiver + "." + e - when /^[0-9]/ - when *Operators - #receiver + " " + e - end + if receiver.match(/^\.{2}\//) + receiver + e.to_s + else + case e + when /^[a-zA-Z_]/ + receiver + "." + e + when /^[0-9]/ + when *Operators + #receiver + " " + e + end + end end end end diff --git a/lib/pry/default_commands/input_and_output.rb b/lib/pry/default_commands/input_and_output.rb index 32c59bd5..eb50ea70 100644 --- a/lib/pry/default_commands/input_and_output.rb +++ b/lib/pry/default_commands/input_and_output.rb @@ -33,7 +33,7 @@ class Pry else _pry_.push_prompt Pry::SHELL_PROMPT _pry_.custom_completions = Pry::FILE_COMPLETIONS - Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, + Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, _pry_, _pry_.instance_eval(&Pry::FILE_COMPLETIONS) end end diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 93a8a303..797bd556 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -359,7 +359,7 @@ class Pry @indent.reset if eval_string.empty? current_prompt = select_prompt(eval_string, target) - completion_proc = Pry::InputCompleter.build_completion_proc(target, + completion_proc = Pry::InputCompleter.build_completion_proc(target, self, instance_eval(&custom_completions)) diff --git a/test/test_completion.rb b/test/test_completion.rb index 8b0b3d4c..50b91ba7 100644 --- a/test/test_completion.rb +++ b/test/test_completion.rb @@ -58,5 +58,27 @@ describe Pry::InputCompleter do completer.call('@@nu').include?('@@number').should == true completer.call('@@number.cl').include?('@@number.class').should == true end + + it 'should complete previous scope' do + module Bar + @barvar = :bar + end + + module Baz + @bar = Bar + @bazvar = :baz + end + + pry = Pry.new() + stack = pry.binding_stack + stack.push(Pry.binding_for(Baz)) + stack.push(Pry.binding_for(Bar)) + + completer = Pry::InputCompleter.build_completion_proc( + Pry.binding_for(Bar), pry + ) + + completer.call('../@').include?("../@bazvar").should == true + end end From cfd36d86cee7a2c4f57bada1566bd1b04dec1992 Mon Sep 17 00:00:00 2001 From: Ingrid Date: Sat, 4 Aug 2012 15:39:46 -0400 Subject: [PATCH 2/7] Add get_context abstraction to cd. --- lib/pry/default_commands/cd.rb | 112 ++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/lib/pry/default_commands/cd.rb b/lib/pry/default_commands/cd.rb index cbdbbab3..d0934dcc 100644 --- a/lib/pry/default_commands/cd.rb +++ b/lib/pry/default_commands/cd.rb @@ -22,60 +22,70 @@ class Pry BANNER def process - # Extract command arguments. Delete blank arguments like " ", but - # don't delete empty strings like "". - path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } - stack = _pry_.binding_stack.dup - old_stack = state.old_stack || [] - - # Special case when we only get a single "/", return to root. - if path.empty? - state.old_stack = stack.dup unless old_stack.empty? - stack = [stack.first] - end - - path.each_with_index do |context, i| - begin - case context.chomp - when "" - state.old_stack = stack.dup - stack = [stack.first] - when "::" - state.old_stack = stack.dup - stack.push(TOPLEVEL_BINDING) - when "." - next - when ".." - unless stack.size == 1 - # Don't rewrite old_stack if we're in complex expression - # (e.g.: `cd 1/2/3/../4). - state.old_stack = stack.dup if path.first == ".." - stack.pop - end - when "-" - unless old_stack.empty? - # Interchange current stack and old stack with each other. - stack, state.old_stack = state.old_stack, stack - end - else - state.old_stack = stack.dup if i == 0 - stack.push(Pry.binding_for(stack.last.eval(context))) - end - - rescue RescuableException => e - # Restore old stack to its initial values. - state.old_stack = old_stack - - output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" - output.puts e.inspect - return - end - end - - _pry_.binding_stack = stack + stack, old_stack = Pry.get_context(arg_string, _pry_, state.old_stack||[]) + state.old_stack = old_stack + _pry_.binding_stack = stack unless stack.nil? end end end end + + def self.get_context(arg_string, pry=nil, old_stack=[]) + + # Extract command arguments. Delete blank arguments like " ", but + # don't delete empty strings like "". + path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } + stack = pry.binding_stack.dup + state_old_stack = old_stack + + # Special case when we only get a single "/", return to root. + if path.empty? + state_old_stack = stack.dup unless old_stack.empty? + stack = [stack.first] + end + + path.each_with_index do |context, i| + begin + case context.chomp + when "" + state_old_stack = stack.dup + stack = [stack.first] + when "::" + state_old_stack = stack.dup + stack.push(TOPLEVEL_BINDING) + when "." + next + when ".." + unless stack.size == 1 + # Don't rewrite old_stack if we're in complex expression + # (e.g.: `cd 1/2/3/../4). + state_old_stack = stack.dup if path.first == ".." + stack.pop + end + when "-" + unless old_stack.empty? + # Interchange current stack and old stack with each other. + stack, state_old_stack = state_old_stack, stack + end + else + state_old_stack = stack.dup if i == 0 + stack.push(Pry.binding_for(stack.last.eval(context))) + end + + rescue RescuableException => e + + # Restore old stack to its initial values. + state_old_stack = old_stack + + output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" + output.puts e.inspect + return nil, state_old_stack + end + end + return stack, state_old_stack + end + + end + From f4af4eadbb6bb8b015cfaed9d88a6879811380b2 Mon Sep 17 00:00:00 2001 From: John Mair Date: Mon, 6 Aug 2012 07:36:00 +1200 Subject: [PATCH 3/7] Renamed Pry.get_context and moved to a more suitable place --- lib/pry/default_commands/cd.rb | 58 +---------------------- lib/pry/helpers/base_helpers.rb | 82 ++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/lib/pry/default_commands/cd.rb b/lib/pry/default_commands/cd.rb index d0934dcc..97a435f4 100644 --- a/lib/pry/default_commands/cd.rb +++ b/lib/pry/default_commands/cd.rb @@ -22,7 +22,7 @@ class Pry BANNER def process - stack, old_stack = Pry.get_context(arg_string, _pry_, state.old_stack||[]) + stack, old_stack = context_from_object_path(arg_string, _pry_, state.old_stack||[]) state.old_stack = old_stack _pry_.binding_stack = stack unless stack.nil? end @@ -31,61 +31,5 @@ class Pry end end - def self.get_context(arg_string, pry=nil, old_stack=[]) - - # Extract command arguments. Delete blank arguments like " ", but - # don't delete empty strings like "". - path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } - stack = pry.binding_stack.dup - state_old_stack = old_stack - - # Special case when we only get a single "/", return to root. - if path.empty? - state_old_stack = stack.dup unless old_stack.empty? - stack = [stack.first] - end - - path.each_with_index do |context, i| - begin - case context.chomp - when "" - state_old_stack = stack.dup - stack = [stack.first] - when "::" - state_old_stack = stack.dup - stack.push(TOPLEVEL_BINDING) - when "." - next - when ".." - unless stack.size == 1 - # Don't rewrite old_stack if we're in complex expression - # (e.g.: `cd 1/2/3/../4). - state_old_stack = stack.dup if path.first == ".." - stack.pop - end - when "-" - unless old_stack.empty? - # Interchange current stack and old stack with each other. - stack, state_old_stack = state_old_stack, stack - end - else - state_old_stack = stack.dup if i == 0 - stack.push(Pry.binding_for(stack.last.eval(context))) - end - - rescue RescuableException => e - - # Restore old stack to its initial values. - state_old_stack = old_stack - - output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" - output.puts e.inspect - return nil, state_old_stack - end - end - return stack, state_old_stack - end - - end diff --git a/lib/pry/helpers/base_helpers.rb b/lib/pry/helpers/base_helpers.rb index 01736845..a91283cf 100644 --- a/lib/pry/helpers/base_helpers.rb +++ b/lib/pry/helpers/base_helpers.rb @@ -70,7 +70,7 @@ class Pry end def use_ansi_codes? - windows_ansi? || ENV['TERM'] && ENV['TERM'] != "dumb" + windows_ansi? || ENV['TERM'] && ENV['TERM'] != "dumb" end def colorize_code(code) @@ -141,16 +141,16 @@ class Pry # FIXME! Another JRuby hack def stagger_output(text, out = nil) out ||= case - when respond_to?(:output) - # Mixin. - output - when Pry.respond_to?(:output) - # Parent. - Pry.output - else - # Sys. - $stdout - end + when respond_to?(:output) + # Mixin. + output + when Pry.respond_to?(:output) + # Parent. + Pry.output + else + # Sys. + $stdout + end if text.lines.count < page_size || !Pry.pager out.puts text @@ -222,6 +222,66 @@ class Pry end end + # @param [String] arg_string The object path expressed as a string. + # @param [Pry] _pry_ The relevant Pry instance. + # @param [Array] old_stack The state of the old binding stack + # @return [Array, Array>] An array + # containing two elements: The new `binding_stack` and the old `binding_stack`. + def context_from_object_path(arg_string, _pry_=nil, old_stack=[]) + + # Extract command arguments. Delete blank arguments like " ", but + # don't delete empty strings like "". + path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } + stack = _pry_.binding_stack.dup + state_old_stack = old_stack + + # Special case when we only get a single "/", return to root. + if path.empty? + state_old_stack = stack.dup unless old_stack.empty? + stack = [stack.first] + end + + path.each_with_index do |context, i| + begin + case context.chomp + when "" + state_old_stack = stack.dup + stack = [stack.first] + when "::" + state_old_stack = stack.dup + stack.push(TOPLEVEL_BINDING) + when "." + next + when ".." + unless stack.size == 1 + # Don't rewrite old_stack if we're in complex expression + # (e.g.: `cd 1/2/3/../4). + state_old_stack = stack.dup if path.first == ".." + stack.pop + end + when "-" + unless old_stack.empty? + # Interchange current stack and old stack with each other. + stack, state_old_stack = state_old_stack, stack + end + else + state_old_stack = stack.dup if i == 0 + stack.push(Pry.binding_for(stack.last.eval(context))) + end + + rescue RescuableException => e + + # Restore old stack to its initial values. + state_old_stack = old_stack + + output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" + output.puts e.inspect + return nil, state_old_stack + end + end + return stack, state_old_stack + end + end end end From ef482bb551f692f1ea1260408b4a5881fca26cb0 Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Fri, 3 Aug 2012 21:20:20 -0400 Subject: [PATCH 4/7] add support for tabbing into previous scope --- lib/pry/completion.rb | 34 +++++++++++++++----- lib/pry/default_commands/input_and_output.rb | 2 +- lib/pry/pry_instance.rb | 2 +- test/test_completion.rb | 22 +++++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/lib/pry/completion.rb b/lib/pry/completion.rb index 9d7c913e..e64e1463 100644 --- a/lib/pry/completion.rb +++ b/lib/pry/completion.rb @@ -41,7 +41,7 @@ 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, commands=[""]) + def self.build_completion_proc(target, pry=nil, commands=[""]) proc do |input| begin bind = target @@ -177,6 +177,20 @@ class Pry end select_message(receiver, message, candidates) + when /^(\.{2}\/)(.*)$/ + # previous scope (../) * + stack = pry.binding_stack.dup + stack.pop + if stack.length >= 1 + receiver = $1 + message = $2 + scope = stack[-1] + + ivars = eval("instance_variables", scope) + locals = eval("local_variables", scope) + select_message(receiver, message, ivars + locals) + end + when /^\.([^.]*)$/ # unknown(maybe String) @@ -207,13 +221,17 @@ class Pry def self.select_message(receiver, message, candidates) candidates.grep(/^#{message}/).collect do |e| - case e - when /^[a-zA-Z_]/ - receiver + "." + e - when /^[0-9]/ - when *Operators - #receiver + " " + e - end + if receiver.match(/^\.{2}\//) + receiver + e.to_s + else + case e + when /^[a-zA-Z_]/ + receiver + "." + e + when /^[0-9]/ + when *Operators + #receiver + " " + e + end + end end end end diff --git a/lib/pry/default_commands/input_and_output.rb b/lib/pry/default_commands/input_and_output.rb index 32c59bd5..eb50ea70 100644 --- a/lib/pry/default_commands/input_and_output.rb +++ b/lib/pry/default_commands/input_and_output.rb @@ -33,7 +33,7 @@ class Pry else _pry_.push_prompt Pry::SHELL_PROMPT _pry_.custom_completions = Pry::FILE_COMPLETIONS - Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, + Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, _pry_, _pry_.instance_eval(&Pry::FILE_COMPLETIONS) end end diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 8c905517..532d19b6 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -359,7 +359,7 @@ class Pry @indent.reset if eval_string.empty? current_prompt = select_prompt(eval_string, target) - completion_proc = Pry::InputCompleter.build_completion_proc(target, + completion_proc = Pry::InputCompleter.build_completion_proc(target, self, instance_eval(&custom_completions)) diff --git a/test/test_completion.rb b/test/test_completion.rb index 8b0b3d4c..50b91ba7 100644 --- a/test/test_completion.rb +++ b/test/test_completion.rb @@ -58,5 +58,27 @@ describe Pry::InputCompleter do completer.call('@@nu').include?('@@number').should == true completer.call('@@number.cl').include?('@@number.class').should == true end + + it 'should complete previous scope' do + module Bar + @barvar = :bar + end + + module Baz + @bar = Bar + @bazvar = :baz + end + + pry = Pry.new() + stack = pry.binding_stack + stack.push(Pry.binding_for(Baz)) + stack.push(Pry.binding_for(Bar)) + + completer = Pry::InputCompleter.build_completion_proc( + Pry.binding_for(Bar), pry + ) + + completer.call('../@').include?("../@bazvar").should == true + end end From 5e4463b50d42cbb06c82209a24d0d49638b67c38 Mon Sep 17 00:00:00 2001 From: Ingrid Date: Sat, 4 Aug 2012 15:39:46 -0400 Subject: [PATCH 5/7] Add get_context abstraction to cd. --- lib/pry/default_commands/cd.rb | 112 ++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/lib/pry/default_commands/cd.rb b/lib/pry/default_commands/cd.rb index cbdbbab3..d0934dcc 100644 --- a/lib/pry/default_commands/cd.rb +++ b/lib/pry/default_commands/cd.rb @@ -22,60 +22,70 @@ class Pry BANNER def process - # Extract command arguments. Delete blank arguments like " ", but - # don't delete empty strings like "". - path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } - stack = _pry_.binding_stack.dup - old_stack = state.old_stack || [] - - # Special case when we only get a single "/", return to root. - if path.empty? - state.old_stack = stack.dup unless old_stack.empty? - stack = [stack.first] - end - - path.each_with_index do |context, i| - begin - case context.chomp - when "" - state.old_stack = stack.dup - stack = [stack.first] - when "::" - state.old_stack = stack.dup - stack.push(TOPLEVEL_BINDING) - when "." - next - when ".." - unless stack.size == 1 - # Don't rewrite old_stack if we're in complex expression - # (e.g.: `cd 1/2/3/../4). - state.old_stack = stack.dup if path.first == ".." - stack.pop - end - when "-" - unless old_stack.empty? - # Interchange current stack and old stack with each other. - stack, state.old_stack = state.old_stack, stack - end - else - state.old_stack = stack.dup if i == 0 - stack.push(Pry.binding_for(stack.last.eval(context))) - end - - rescue RescuableException => e - # Restore old stack to its initial values. - state.old_stack = old_stack - - output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" - output.puts e.inspect - return - end - end - - _pry_.binding_stack = stack + stack, old_stack = Pry.get_context(arg_string, _pry_, state.old_stack||[]) + state.old_stack = old_stack + _pry_.binding_stack = stack unless stack.nil? end end end end + + def self.get_context(arg_string, pry=nil, old_stack=[]) + + # Extract command arguments. Delete blank arguments like " ", but + # don't delete empty strings like "". + path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } + stack = pry.binding_stack.dup + state_old_stack = old_stack + + # Special case when we only get a single "/", return to root. + if path.empty? + state_old_stack = stack.dup unless old_stack.empty? + stack = [stack.first] + end + + path.each_with_index do |context, i| + begin + case context.chomp + when "" + state_old_stack = stack.dup + stack = [stack.first] + when "::" + state_old_stack = stack.dup + stack.push(TOPLEVEL_BINDING) + when "." + next + when ".." + unless stack.size == 1 + # Don't rewrite old_stack if we're in complex expression + # (e.g.: `cd 1/2/3/../4). + state_old_stack = stack.dup if path.first == ".." + stack.pop + end + when "-" + unless old_stack.empty? + # Interchange current stack and old stack with each other. + stack, state_old_stack = state_old_stack, stack + end + else + state_old_stack = stack.dup if i == 0 + stack.push(Pry.binding_for(stack.last.eval(context))) + end + + rescue RescuableException => e + + # Restore old stack to its initial values. + state_old_stack = old_stack + + output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" + output.puts e.inspect + return nil, state_old_stack + end + end + return stack, state_old_stack + end + + end + From 5b292ff0f53c8ded20f84d53cccc5daca4f60b94 Mon Sep 17 00:00:00 2001 From: John Mair Date: Mon, 6 Aug 2012 07:36:00 +1200 Subject: [PATCH 6/7] Renamed Pry.get_context and moved to a more suitable place --- lib/pry/default_commands/cd.rb | 58 +---------------------- lib/pry/helpers/base_helpers.rb | 82 ++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/lib/pry/default_commands/cd.rb b/lib/pry/default_commands/cd.rb index d0934dcc..97a435f4 100644 --- a/lib/pry/default_commands/cd.rb +++ b/lib/pry/default_commands/cd.rb @@ -22,7 +22,7 @@ class Pry BANNER def process - stack, old_stack = Pry.get_context(arg_string, _pry_, state.old_stack||[]) + stack, old_stack = context_from_object_path(arg_string, _pry_, state.old_stack||[]) state.old_stack = old_stack _pry_.binding_stack = stack unless stack.nil? end @@ -31,61 +31,5 @@ class Pry end end - def self.get_context(arg_string, pry=nil, old_stack=[]) - - # Extract command arguments. Delete blank arguments like " ", but - # don't delete empty strings like "". - path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } - stack = pry.binding_stack.dup - state_old_stack = old_stack - - # Special case when we only get a single "/", return to root. - if path.empty? - state_old_stack = stack.dup unless old_stack.empty? - stack = [stack.first] - end - - path.each_with_index do |context, i| - begin - case context.chomp - when "" - state_old_stack = stack.dup - stack = [stack.first] - when "::" - state_old_stack = stack.dup - stack.push(TOPLEVEL_BINDING) - when "." - next - when ".." - unless stack.size == 1 - # Don't rewrite old_stack if we're in complex expression - # (e.g.: `cd 1/2/3/../4). - state_old_stack = stack.dup if path.first == ".." - stack.pop - end - when "-" - unless old_stack.empty? - # Interchange current stack and old stack with each other. - stack, state_old_stack = state_old_stack, stack - end - else - state_old_stack = stack.dup if i == 0 - stack.push(Pry.binding_for(stack.last.eval(context))) - end - - rescue RescuableException => e - - # Restore old stack to its initial values. - state_old_stack = old_stack - - output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" - output.puts e.inspect - return nil, state_old_stack - end - end - return stack, state_old_stack - end - - end diff --git a/lib/pry/helpers/base_helpers.rb b/lib/pry/helpers/base_helpers.rb index 01736845..a91283cf 100644 --- a/lib/pry/helpers/base_helpers.rb +++ b/lib/pry/helpers/base_helpers.rb @@ -70,7 +70,7 @@ class Pry end def use_ansi_codes? - windows_ansi? || ENV['TERM'] && ENV['TERM'] != "dumb" + windows_ansi? || ENV['TERM'] && ENV['TERM'] != "dumb" end def colorize_code(code) @@ -141,16 +141,16 @@ class Pry # FIXME! Another JRuby hack def stagger_output(text, out = nil) out ||= case - when respond_to?(:output) - # Mixin. - output - when Pry.respond_to?(:output) - # Parent. - Pry.output - else - # Sys. - $stdout - end + when respond_to?(:output) + # Mixin. + output + when Pry.respond_to?(:output) + # Parent. + Pry.output + else + # Sys. + $stdout + end if text.lines.count < page_size || !Pry.pager out.puts text @@ -222,6 +222,66 @@ class Pry end end + # @param [String] arg_string The object path expressed as a string. + # @param [Pry] _pry_ The relevant Pry instance. + # @param [Array] old_stack The state of the old binding stack + # @return [Array, Array>] An array + # containing two elements: The new `binding_stack` and the old `binding_stack`. + def context_from_object_path(arg_string, _pry_=nil, old_stack=[]) + + # Extract command arguments. Delete blank arguments like " ", but + # don't delete empty strings like "". + path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } + stack = _pry_.binding_stack.dup + state_old_stack = old_stack + + # Special case when we only get a single "/", return to root. + if path.empty? + state_old_stack = stack.dup unless old_stack.empty? + stack = [stack.first] + end + + path.each_with_index do |context, i| + begin + case context.chomp + when "" + state_old_stack = stack.dup + stack = [stack.first] + when "::" + state_old_stack = stack.dup + stack.push(TOPLEVEL_BINDING) + when "." + next + when ".." + unless stack.size == 1 + # Don't rewrite old_stack if we're in complex expression + # (e.g.: `cd 1/2/3/../4). + state_old_stack = stack.dup if path.first == ".." + stack.pop + end + when "-" + unless old_stack.empty? + # Interchange current stack and old stack with each other. + stack, state_old_stack = state_old_stack, stack + end + else + state_old_stack = stack.dup if i == 0 + stack.push(Pry.binding_for(stack.last.eval(context))) + end + + rescue RescuableException => e + + # Restore old stack to its initial values. + state_old_stack = old_stack + + output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" + output.puts e.inspect + return nil, state_old_stack + end + end + return stack, state_old_stack + end + end end end From 3841563fb248477eb3306742e3132775ec4115dc Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Mon, 6 Aug 2012 15:39:57 -0400 Subject: [PATCH 7/7] add support tab-completing arbitrarily long paths --- lib/pry/completion.rb | 142 +++++++++++++++++++++++----------------- test/test_completion.rb | 102 +++++++++++++++++++++++------ 2 files changed, 164 insertions(+), 80 deletions(-) diff --git a/lib/pry/completion.rb b/lib/pry/completion.rb index e64e1463..45b2d061 100644 --- a/lib/pry/completion.rb +++ b/lib/pry/completion.rb @@ -43,34 +43,48 @@ class Pry # @param [Array] commands The array of Pry commands. def self.build_completion_proc(target, pry=nil, commands=[""]) proc do |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 case input + + + # Complete stdlib symbols + when /^(\/[^\/]*\/)\.([^.]*)$/ # Regexp receiver = $1 message = Regexp.quote($2) - candidates = Regexp.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) + candidates = Regexp.instance_methods.collect(&:to_s) + select_message(path, receiver, message, candidates) when /^([^\]]*\])\.([^.]*)$/ # Array receiver = $1 message = Regexp.quote($2) - candidates = Array.instance_methods.collect{|m| m.to_s} - select_message(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) - candidates = Proc.instance_methods.collect{|m| m.to_s} - candidates |= Hash.instance_methods.collect{|m| m.to_s} - select_message(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 @@ -86,16 +100,25 @@ class Pry when /^::([A-Z][^:\.\(]*)$/ # Absolute Constant or class methods receiver = $1 - candidates = Object.constants.collect{|m| m.to_s} + candidates = Object.constants.collect(&:to_s) candidates.grep(/^#{receiver}/).collect{|e| "::" + e} - when /^([A-Z].*)::([^:.]*)$/ + + # Complete target symbols + + when /^([A-Z][A-Za-z0-9]*)$/ + # Constant + message = $1 + candidates = target.eval("self.class.constants").collect(&:to_s) + candidates.grep(/^#{message}/).collect(&path) + + when /^([A-Z].*)::([^:.]*)$/ # Constant or class methods receiver = $1 message = Regexp.quote($2) begin - candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) - candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) + candidates = eval("#{receiver}.constants.collect(&:to_s)", bind) + candidates |= eval("#{receiver}.methods.collect(&:to_s)", bind) rescue RescuableException candidates = [] end @@ -106,8 +129,8 @@ class Pry receiver = $1 message = Regexp.quote($2) - candidates = Symbol.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) + candidates = Symbol.instance_methods.collect(&:to_s) + select_message(path, receiver, message, candidates) when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/ # Numeric @@ -115,42 +138,43 @@ class Pry message = Regexp.quote($5) begin - candidates = eval(receiver, bind).methods.collect{|m| m.to_s} + candidates = eval(receiver, bind).methods.collect(&:to_s) rescue RescuableException candidates = [] end - select_message(receiver, message, candidates) + select_message(path, receiver, message, candidates) - when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/ + when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/# # Numeric(0xFFFF) receiver = $1 message = Regexp.quote($2) begin - candidates = eval(receiver, bind).methods.collect{|m| m.to_s} + candidates = eval(receiver, bind).methods.collect(&:to_s) rescue RescuableException candidates = [] end - select_message(receiver, message, candidates) + select_message(path, receiver, message, candidates) when /^(\$[^.]*)$/ + # Global variables regmessage = Regexp.new(Regexp.quote($1)) - candidates = global_variables.collect{|m| m.to_s}.grep(regmessage) + candidates = global_variables.collect(&:to_s).grep(regmessage) - when /^([^."].*)\.([^.]*)$/ - # variable + when /^([^."].*)\.([^.]*)$/ + # Variable receiver = $1 message = Regexp.quote($2) - gv = eval("global_variables", bind).collect{|m| m.to_s} - lv = eval("local_variables", bind).collect{|m| m.to_s} - cv = eval("self.class.constants", bind).collect{|m| m.to_s} + 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{|m| m.to_s} + candidates = eval("#{receiver}.methods", bind).collect(&:to_s) rescue RescuableException candidates = [] end @@ -169,49 +193,35 @@ class Pry # jruby doesn't always provide #instance_methods() on each # object. if m.respond_to?(:instance_methods) - candidates.concat m.instance_methods(false).collect{|x| x.to_s} + candidates.concat m.instance_methods(false).collect(&:to_s) end } candidates.sort! candidates.uniq! end - select_message(receiver, message, candidates) - - when /^(\.{2}\/)(.*)$/ - # previous scope (../) * - stack = pry.binding_stack.dup - stack.pop - if stack.length >= 1 - receiver = $1 - message = $2 - scope = stack[-1] - - ivars = eval("instance_variables", scope) - locals = eval("local_variables", scope) - select_message(receiver, message, ivars + locals) - end + select_message(path, receiver, message, candidates) when /^\.([^.]*)$/ - # unknown(maybe String) - + # Unknown(maybe String) receiver = "" message = Regexp.quote($1) - candidates = String.instance_methods(true).collect{|m| m.to_s} - select_message(receiver, message, candidates) + 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{|m| m.to_s} + ).collect(&:to_s) if eval("respond_to?(:class_variables)", bind) - candidates += eval("class_variables", bind).collect { |m| m.to_s } + candidates += eval("class_variables", bind).collect(&:to_s) end - - (candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/) + candidates = (candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/) + candidates.collect(&path) end rescue RescuableException [] @@ -219,21 +229,33 @@ class Pry end end - def self.select_message(receiver, message, candidates) + def self.select_message(path, receiver, message, candidates) candidates.grep(/^#{message}/).collect do |e| - if receiver.match(/^\.{2}\//) - receiver + e.to_s - else - case e - when /^[a-zA-Z_]/ - receiver + "." + e - when /^[0-9]/ - when *Operators - #receiver + " " + e - end + case e + when /^[a-zA-Z_]/ + path.call(receiver + "." + e) + when /^[0-9]/ + when *Operators + #receiver + " " + e end end end + + def self.build_path(input) + return proc {|input| input.to_s }, input if input[/\/\./] + + trailing_slash = input[-1] == '/' + contexts = input.chomp('/').split(/\//) + input = contexts[-1] + + path = proc do |input| + p = contexts[0..-2].push(input).join('/') + p += '/' if trailing_slash && !input.nil? + p + end + + return path, input + end end end diff --git a/test/test_completion.rb b/test/test_completion.rb index 50b91ba7..0d0b5192 100644 --- a/test/test_completion.rb +++ b/test/test_completion.rb @@ -1,5 +1,16 @@ 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} + return proc {|*symbols| symbols.each(&test) } +end + + describe Pry::InputCompleter do before do @@ -33,33 +44,83 @@ describe Pry::InputCompleter do it 'should complete instance variables' do object = Object.new + # set variables in appropriate scope object.instance_variable_set(:'@name', 'Pry') object.class.send(:class_variable_set, :'@@number', 10) - object.instance_variables.map { |v| v.to_sym } \ - .include?(:'@name').should == true + # check to see if variables are in scope + object.instance_variables. + map { |v| v.to_sym }. + include?(:'@name').should == true - object.class.class_variables.map { |v| v.to_sym } \ - .include?(:'@@number').should == true - - completer = Pry::InputCompleter.build_completion_proc( - Pry.binding_for(object) - ) + object.class.class_variables. + map { |v| v.to_sym }. + include?(:'@@number').should == true # Complete instance variables. - completer.call('@na').include?('@name').should == true - completer.call('@name.down').include?('@name.downcase').should == true + b = Pry.binding_for(object) + completer_test(b).call('@name', '@name.downcase') # Complete class variables. - completer = Pry::InputCompleter.build_completion_proc( - Pry.binding_for(object.class) - ) + b = Pry.binding_for(object.class) + completer_test(b).call('@@number', '@@number.class') - completer.call('@@nu').include?('@@number').should == true - completer.call('@@number.cl').include?('@@number.class').should == true end - it 'should complete previous scope' do + it 'should complete for stdlib symbols' do + + o = Object.new + # Regexp + completer_test(o).call('/foo/.extend') + + # Array + completer_test(o).call('[1].push') + + # Hash + completer_test(o).call('{"a" => "b"}.keys') + + # Proc + completer_test(o).call('{2}.call') + + # Symbol + completer_test(o).call(':symbol.to_s') + + # Absolute Constant + completer_test(o).call('::IndexError') + end + + it 'should complete for target symbols' do + o = Object.new + + # Constant + module Mod + Con = 'Constant' + module Mod2 + end + end + + completer_test(Mod).call('Con') + + # Constants or Class Methods + completer_test(o).call('Mod::Con') + + # Symbol + foo = :symbol + completer_test(o).call(':symbol') + + # Variables + class << o + attr_accessor :foo + end + o.foo = 'bar' + completer_test(binding).call('o.foo') + + # trailing slash + new_completer(Mod).call('Mod2/').include?('Mod2/').should == true + + end + + it 'should complete for arbitrary scopes' do module Bar @barvar = :bar end @@ -74,11 +135,12 @@ describe Pry::InputCompleter do stack.push(Pry.binding_for(Baz)) stack.push(Pry.binding_for(Bar)) - completer = Pry::InputCompleter.build_completion_proc( - Pry.binding_for(Bar), pry - ) + b = Pry.binding_for(Bar) + completer_test(b, pry).call("../@bazvar") + + completer_test(binding, pry).call('/Bar') - completer.call('../@').include?("../@bazvar").should == true end + end