Permalink
Browse files

Be more careful exception handling in Readline [Fixes #632, #605]

Firstly wrap the entire completion_proc in a begin/rescue/end so that
even if something we're not expecting goes wrong, we don't fail.

Secondly, only throw the user out of pry if reading from readline fails
five times in a row. This avoids a transient error (like a time-out, or
a failed tab completion) from kicking you out of pry; but also avoids
the infinite looping problem that can happen if the error is not transient.
  • Loading branch information...
1 parent 94fbfab commit b5cb8b142f116364018c550c6d1e61887ad7a071 @ConradIrwin ConradIrwin committed Jul 4, 2012
Showing with 119 additions and 129 deletions.
  1. +113 −129 lib/pry/completion.rb
  2. +6 −0 lib/pry/pry_instance.rb
View
242 lib/pry/completion.rb
@@ -43,160 +43,144 @@ module InputCompleter
# @param [Array<String>] commands The array of Pry commands.
def self.build_completion_proc(target, commands=[""])
proc do |input|
- bind = target
-
- case input
- when /^(\/[^\/]*\/)\.([^.]*)$/
- # Regexp
- receiver = $1
- message = Regexp.quote($2)
-
- candidates = Regexp.instance_methods.collect{|m| m.to_s}
- select_message(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)
-
- 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)
-
- when /^(:[^:.]*)$/
- # Symbol
- if Symbol.respond_to?(:all_symbols)
- sym = Regexp.quote($1)
- candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name}
+ begin
+ bind = target
+
+ case input
+ when /^(\/[^\/]*\/)\.([^.]*)$/
+ # Regexp
+ receiver = $1
+ message = Regexp.quote($2)
+
+ candidates = Regexp.instance_methods.collect{|m| m.to_s}
+ select_message(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)
+
+ 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)
+
+ 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
- candidates.grep(/^#{sym}/)
- else
- []
- end
+ when /^::([A-Z][^:\.\(]*)$/
+ # Absolute Constant or class methods
+ receiver = $1
+ candidates = Object.constants.collect{|m| m.to_s}
+ candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
- when /^::([A-Z][^:\.\(]*)$/
- # Absolute Constant or class methods
- receiver = $1
- candidates = Object.constants.collect{|m| m.to_s}
- candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
-
- when /^([A-Z].*)::([^:.]*)$/
- # Constant or class methods
- receiver = $1
- message = Regexp.quote($2)
- begin
+ when /^([A-Z].*)::([^:.]*)$/
+ # Constant or class methods
+ receiver = $1
+ message = Regexp.quote($2)
candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind)
candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind)
- rescue RescuableException
- candidates = []
- end
- candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
+ candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
- when /^(:[^:.]+)\.([^.]*)$/
- # Symbol
- receiver = $1
- message = Regexp.quote($2)
+ when /^(:[^:.]+)\.([^.]*)$/
+ # Symbol
+ 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{|m| m.to_s}
+ select_message(receiver, message, candidates)
- when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/
- # Numeric
- receiver = $1
- message = Regexp.quote($5)
+ when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/
+ # Numeric
+ receiver = $1
+ message = Regexp.quote($5)
- begin
candidates = eval(receiver, bind).methods.collect{|m| m.to_s}
- rescue RescuableException
- candidates = []
- end
- select_message(receiver, message, candidates)
+ select_message(receiver, message, candidates)
- when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/
- # Numeric(0xFFFF)
- receiver = $1
- message = Regexp.quote($2)
+ 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}
- rescue RescuableException
- candidates = []
- end
- select_message(receiver, message, candidates)
+ select_message(receiver, message, candidates)
- when /^(\$[^.]*)$/
- regmessage = Regexp.new(Regexp.quote($1))
- candidates = global_variables.collect{|m| m.to_s}.grep(regmessage)
+ when /^(\$[^.]*)$/
+ regmessage = Regexp.new(Regexp.quote($1))
+ candidates = global_variables.collect{|m| m.to_s}.grep(regmessage)
- when /^([^."].*)\.([^.]*)$/
- # variable
- receiver = $1
- message = Regexp.quote($2)
+ 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{|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}
- if (gv | lv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver
- # foo.func and foo is local var. OR
- # Foo::Bar.func
- begin
+ if (gv | lv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver
+ # foo.func and foo is local var. OR
+ # Foo::Bar.func
candidates = eval("#{receiver}.methods", bind).collect{|m| m.to_s}
- rescue RescuableException
+ else
+ # func1.func2
candidates = []
- end
- else
- # func1.func2
- candidates = []
- ObjectSpace.each_object(Module){|m|
- begin
+ ObjectSpace.each_object(Module){|m|
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{|x| x.to_s}
- end
- }
- candidates.sort!
- candidates.uniq!
- end
- select_message(receiver, message, candidates)
+ 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{|x| x.to_s}
+ end
+ }
+ candidates.sort!
+ candidates.uniq!
+ end
+ select_message(receiver, message, candidates)
- when /^\.([^.]*)$/
- # unknown(maybe String)
+ when /^\.([^.]*)$/
+ # unknown(maybe String)
- receiver = ""
- message = Regexp.quote($1)
+ 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{|m| m.to_s}
+ select_message(receiver, message, candidates)
- else
- candidates = eval(
- "methods | private_methods | local_variables | " \
- "self.class.constants | instance_variables",
- bind
- ).collect{|m| m.to_s}
+ else
+ candidates = eval(
+ "methods | private_methods | local_variables | " \
+ "self.class.constants | instance_variables",
+ bind
+ ).collect{|m| m.to_s}
+
+ if eval("respond_to?(:class_variables)", bind)
+ candidates += eval("class_variables", bind).collect { |m| m.to_s }
+ end
- if eval("respond_to?(:class_variables)", bind)
- candidates += eval("class_variables", bind).collect { |m| m.to_s }
+ (candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/)
end
-
- (candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/)
+ rescue RescuableException
+ []
end
end
end
View
6 lib/pry/pry_instance.rb
@@ -539,6 +539,7 @@ def last_result_is_exception?
# Manage switching of input objects on encountering EOFErrors
def handle_read_errors
should_retry = true
+ exception_count = 0
begin
yield
rescue EOFError
@@ -565,6 +566,11 @@ def handle_read_errors
# anything about it.
rescue RescuableException => e
puts "Error: #{e.message}"
+ output.puts e.backtrace
+ exception_count += 1
+ if exception_count < 5
+ retry
+ end
puts "FATAL: Pry failed to get user input using `#{input}`."
puts "To fix this you may be able to pass input and output file descriptors to pry directly. e.g."
puts " Pry.config.input = STDIN"

0 comments on commit b5cb8b1

Please sign in to comment.