├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── pry-debugger.rb └── pry-debugger │ ├── base.rb │ ├── breakpoints.rb │ ├── cli.rb │ ├── commands.rb │ ├── processor.rb │ ├── pry_ext.rb │ ├── pry_remote_ext.rb │ └── version.rb └── pry-debugger.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.3 (2014-07-02) 2 | 3 | * Support [pry] 0.10 4 | 5 | 6 | ## 0.2.2 (2013-03-07) 7 | 8 | * Relaxed [debugger][debugger] dependency. 9 | 10 | 11 | ## 0.2.1 (2012-12-26) 12 | 13 | * Support breakpoints on methods defined in the pry console. (@banister) 14 | * Fix support for specifying breakpoints by *file:line_number*. (@nviennot) 15 | * Validate breakpoint conditionals are real Ruby expressions. 16 | * Support for [debugger][debugger] ~> 1.2.0. (@jshou) 17 | * Safer `alias_method_chain`-style patching of `Pry.start` and 18 | `PryRemote::Server#teardown`. (@benizi) 19 | 20 | 21 | ## 0.2.0 (2012-06-11) 22 | 23 | * Breakpoints 24 | * **finish** command 25 | * Internal cleanup and bug fixes 26 | 27 | 28 | ## 0.1.0 (2012-06-07) 29 | 30 | * First release. **step**, **next**, and **continue** commands. 31 | [pry-remote 0.1.4][pry-remote] support. 32 | 33 | 34 | [pry]: http://pryrepl.org/ 35 | [pry-remote]: https://github.com/Mon-Ouie/pry-remote 36 | [debugger]: https://github.com/cldwalker/debugger 37 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/Expat License 2 | 3 | Copyright (c) 2012 by Gopal Patel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Using MRI 2.0.0+? Use [**pry-byebug**][pry-byebug]. 2 | 3 | * * * 4 | 5 | pry-debugger [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/nixme/pry-debugger/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 6 | ============ 7 | 8 | _Fast execution control in Pry_ 9 | 10 | Adds **step**, **next**, **finish**, and **continue** commands and 11 | **breakpoints** to [Pry][pry] using [debugger][debugger]. 12 | 13 | To use, invoke pry normally. No need to start your script or app differently. 14 | 15 | ```ruby 16 | def some_method 17 | binding.pry # Execution will stop here. 18 | puts 'Hello World' # Run 'step' or 'next' in the console to move here. 19 | end 20 | ``` 21 | 22 | For a complete debugging environment, add 23 | [pry-stack_explorer][pry-stack_explorer] for call-stack frame navigation. 24 | 25 | 26 | ## Execution Commands 27 | 28 | **step:** Step execution into the next line or method. Takes an optional numeric 29 | argument to step multiple times. 30 | 31 | **next:** Step over to the next line within the same frame. Also takes an 32 | optional numeric argument to step multiple lines. 33 | 34 | **finish:** Execute until current stack frame returns. 35 | 36 | **continue:** Continue program execution and end the Pry session. 37 | 38 | 39 | ## Breakpoints 40 | 41 | You can set and adjust breakpoints directly from a Pry session using the 42 | following commands: 43 | 44 | **break:** Set a new breakpoint from a line number in the current file, a file 45 | and line number, or a method. Pass an optional expression to create a 46 | conditional breakpoint. Edit existing breakpoints via various flags. 47 | 48 | Examples: 49 | 50 | ``` 51 | break SomeClass#run Break at the start of `SomeClass#run`. 52 | break Foo#bar if baz? Break at `Foo#bar` only if `baz?`. 53 | break app/models/user.rb:15 Break at line 15 in user.rb. 54 | break 14 Break at line 14 in the current file. 55 | 56 | break --condition 4 x > 2 Change condition on breakpoint #4 to 'x > 2'. 57 | break --condition 3 Remove the condition on breakpoint #3. 58 | 59 | break --delete 5 Delete breakpoint #5. 60 | break --disable-all Disable all breakpoints. 61 | 62 | break List all breakpoints. (Same as `breakpoints`) 63 | break --show 2 Show details about breakpoint #2. 64 | ``` 65 | 66 | Type `break --help` from a Pry session to see all available options. 67 | 68 | 69 | **breakpoints**: List all defined breakpoints. Pass `-v` or `--verbose` to see 70 | the source code around each breakpoint. 71 | 72 | 73 | ## Caveats 74 | 75 | **pry-debugger** is not yet thread-safe, so only use in single-threaded 76 | environments. 77 | 78 | Only supports MRI 1.9.2 and 1.9.3. For a pure ruby approach not reliant on 79 | [debugger][debugger], check out [pry-nav][pry-nav]. Note: *pry-nav* and 80 | *pry-debugger* cannot be loaded together. 81 | 82 | 83 | ## Remote debugging 84 | 85 | Support for [pry-remote][pry-remote] (>= 0.1.4) is also included. Requires 86 | explicity requiring *pry-debugger*, not just relying on pry's plugin loader. 87 | 88 | Want to debug a Rails app running inside [foreman][foreman]? Add to your 89 | Gemfile: 90 | 91 | ```ruby 92 | gem 'pry' 93 | gem 'pry-remote' 94 | gem 'pry-stack_explorer' 95 | gem 'pry-debugger' 96 | ``` 97 | 98 | Then add `binding.remote_pry` where you want to pause: 99 | 100 | ```ruby 101 | class UsersController < ApplicationController 102 | def index 103 | binding.remote_pry 104 | ... 105 | end 106 | end 107 | ``` 108 | 109 | Load a page that triggers the code. Connect to the session: 110 | 111 | ``` 112 | $ bundle exec pry-remote 113 | ``` 114 | 115 | Using Pry with Rails? Check out [Jazz Hands][jazz_hands]. 116 | 117 | 118 | ## Tips 119 | 120 | Stepping through code often? Add the following shortcuts to `~/.pryrc`: 121 | 122 | ```ruby 123 | if defined?(PryDebugger) 124 | Pry.commands.alias_command 'c', 'continue' 125 | Pry.commands.alias_command 's', 'step' 126 | Pry.commands.alias_command 'n', 'next' 127 | Pry.commands.alias_command 'f', 'finish' 128 | end 129 | ``` 130 | 131 | 132 | ## Contributors 133 | 134 | * Gopal Patel (@nixme) 135 | * John Mair (@banister) 136 | * Nicolas Viennot (@nviennot) 137 | * Benjamin R. Haskell (@benizi) 138 | * Joshua Hou (@jshou) 139 | * ...and others who helped with [pry-nav][pry-nav] 140 | 141 | Patches and bug reports are welcome. Just send a [pull request][pullrequests] or 142 | file an [issue][issues]. [Project changelog][changelog]. 143 | 144 | 145 | 146 | [pry]: http://pry.github.com 147 | [debugger]: https://github.com/cldwalker/debugger 148 | [pry-stack_explorer]: https://github.com/pry/pry-stack_explorer 149 | [pry-nav]: https://github.com/nixme/pry-nav 150 | [pry-remote]: https://github.com/Mon-Ouie/pry-remote 151 | [foreman]: https://github.com/ddollar/foreman 152 | [jazz_hands]: https://github.com/nixme/jazz_hands 153 | [pullrequests]: https://github.com/nixme/pry-debugger/pulls 154 | [issues]: https://github.com/nixme/pry-debugger/issues 155 | [changelog]: https://github.com/nixme/pry-debugger/blob/master/CHANGELOG.md 156 | [pry-byebug]: https://github.com/deivid-rodriguez/pry-byebug 157 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | -------------------------------------------------------------------------------- /lib/pry-debugger.rb: -------------------------------------------------------------------------------- 1 | require 'pry-debugger/cli' 2 | 3 | # Load pry-remote monkey patches if pry-remote's available 4 | begin 5 | require 'pry-debugger/pry_remote_ext' 6 | rescue LoadError 7 | end 8 | -------------------------------------------------------------------------------- /lib/pry-debugger/base.rb: -------------------------------------------------------------------------------- 1 | module PryDebugger 2 | TRACE_IGNORE_FILES = Dir[File.join(File.dirname(__FILE__), '..', '**', '*.rb')].map { |f| File.expand_path(f) } 3 | 4 | extend self 5 | 6 | # Checks that a binding is in a local file context. Extracted from 7 | # https://github.com/pry/pry/blob/master/lib/pry/default_commands/context.rb 8 | def check_file_context(target) 9 | file = target.eval('__FILE__') 10 | file == Pry.eval_path || (file !~ /(\(.*\))|<.*>/ && file != '' && file != '-e') 11 | end 12 | 13 | # Reference to currently running pry-remote server. Used by the processor. 14 | attr_accessor :current_remote_server 15 | end 16 | -------------------------------------------------------------------------------- /lib/pry-debugger/breakpoints.rb: -------------------------------------------------------------------------------- 1 | module PryDebugger 2 | 3 | # Wrapper for Debugger.breakpoints that respects our Processor and has better 4 | # failure behavior. Acts as an Enumerable. 5 | # 6 | module Breakpoints 7 | extend Enumerable 8 | extend self 9 | 10 | 11 | # Add a new breakpoint. 12 | def add(file, line, expression = nil) 13 | real_file = (file != Pry.eval_path) 14 | raise ArgumentError, 'Invalid file!' if real_file && !File.exist?(file) 15 | validate_expression expression 16 | 17 | Pry.processor.debugging = true 18 | 19 | path = (real_file ? File.expand_path(file) : file) 20 | Debugger.add_breakpoint(path, line, expression) 21 | end 22 | 23 | # Change the conditional expression for a breakpoint. 24 | def change(id, expression = nil) 25 | validate_expression expression 26 | 27 | breakpoint = find_by_id(id) 28 | breakpoint.expr = expression 29 | breakpoint 30 | end 31 | 32 | # Delete an existing breakpoint with the given ID. 33 | def delete(id) 34 | unless Debugger.started? && Debugger.remove_breakpoint(id) 35 | raise ArgumentError, "No breakpoint ##{id}" 36 | end 37 | Pry.processor.debugging = false if to_a.empty? 38 | end 39 | 40 | # Delete all breakpoints. 41 | def clear 42 | Debugger.breakpoints.clear if Debugger.started? 43 | Pry.processor.debugging = false 44 | end 45 | 46 | # Enable a disabled breakpoint with the given ID. 47 | def enable(id) 48 | change_status id, true 49 | end 50 | 51 | # Disable a breakpoint with the given ID. 52 | def disable(id) 53 | change_status id, false 54 | end 55 | 56 | # Disable all breakpoints. 57 | def disable_all 58 | each do |breakpoint| 59 | breakpoint.enabled = false 60 | end 61 | end 62 | 63 | def to_a 64 | Debugger.started? ? Debugger.breakpoints : [] 65 | end 66 | 67 | def size 68 | to_a.size 69 | end 70 | 71 | def each(&block) 72 | to_a.each(&block) 73 | end 74 | 75 | def find_by_id(id) 76 | breakpoint = find { |b| b.id == id } 77 | raise ArgumentError, "No breakpoint ##{id}!" unless breakpoint 78 | breakpoint 79 | end 80 | 81 | 82 | private 83 | 84 | def change_status(id, enabled = true) 85 | breakpoint = find_by_id(id) 86 | breakpoint.enabled = enabled 87 | breakpoint 88 | end 89 | 90 | def validate_expression(expression) 91 | if expression && # `nil` implies no expression given, so pass 92 | (expression.empty? || !Pry::Code.complete_expression?(expression)) 93 | raise "Invalid breakpoint conditional: #{expression}" 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/pry-debugger/cli.rb: -------------------------------------------------------------------------------- 1 | # Pry's new plugin loading system ensures this file runs before pry-remote. So 2 | # attempting to load everything directly from lib/pry-debugger.rb and 3 | # referencing that here causes a circular dependency when running 4 | # bin/pry-remote. 5 | # 6 | # So delay loading our monkey-patch to when someone explicity does a: 7 | # 8 | # require 'pry-debugger' 9 | # 10 | # Load everything else here. 11 | # 12 | 13 | require 'pry-debugger/base' 14 | require 'pry-debugger/pry_ext' 15 | require 'pry-debugger/commands' 16 | -------------------------------------------------------------------------------- /lib/pry-debugger/commands.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'pry-debugger/breakpoints' 3 | 4 | module PryDebugger 5 | Commands = Pry::CommandSet.new do 6 | create_command 'step' do 7 | description 'Step execution into the next line or method.' 8 | 9 | banner <<-BANNER 10 | Usage: step [TIMES] 11 | 12 | Step execution forward. By default, moves a single step. 13 | 14 | Examples: 15 | 16 | step Move a single step forward. 17 | step 5 Execute the next 5 steps. 18 | BANNER 19 | 20 | def process 21 | check_file_context 22 | breakout_navigation :step, args.first 23 | end 24 | end 25 | 26 | 27 | create_command 'next' do 28 | description 'Execute the next line within the current stack frame.' 29 | 30 | banner <<-BANNER 31 | Usage: next [LINES] 32 | 33 | Step over within the same frame. By default, moves forward a single 34 | line. 35 | 36 | Examples: 37 | 38 | next Move a single line forward. 39 | next 4 Execute the next 4 lines. 40 | BANNER 41 | 42 | def process 43 | check_file_context 44 | breakout_navigation :next, args.first 45 | end 46 | end 47 | 48 | 49 | create_command 'finish' do 50 | description 'Execute until current stack frame returns.' 51 | 52 | def process 53 | check_file_context 54 | breakout_navigation :finish 55 | end 56 | end 57 | 58 | 59 | create_command 'continue' do 60 | description 'Continue program execution and end the Pry session.' 61 | 62 | def process 63 | check_file_context 64 | run 'exit-all' 65 | end 66 | end 67 | 68 | 69 | create_command 'break' do 70 | description 'Set or edit a breakpoint.' 71 | 72 | banner <<-BANNER 73 | Usage: break [if CONDITION] 74 | break --condition N [CONDITION] 75 | break [--show | --delete | --enable | --disable] N 76 | break [--delete-all | --disable-all] 77 | Aliases: breakpoint 78 | 79 | Set a breakpoint. Accepts a line number in the current file, a file and 80 | line number, or a method, and an optional condition. 81 | 82 | Pass appropriate flags to manipulate existing breakpoints. 83 | 84 | Examples: 85 | 86 | break SomeClass#run Break at the start of `SomeClass#run`. 87 | break Foo#bar if baz? Break at `Foo#bar` only if `baz?`. 88 | break app/models/user.rb:15 Break at line 15 in user.rb. 89 | break 14 Break at line 14 in the current file. 90 | 91 | break --condition 4 x > 2 Add/change condition on breakpoint #4. 92 | break --condition 3 Remove the condition on breakpoint #3. 93 | 94 | break --delete 5 Delete breakpoint #5. 95 | break --disable-all Disable all breakpoints. 96 | 97 | break List all breakpoints. (Same as `breakpoints`) 98 | break --show 2 Show details about breakpoint #2. 99 | BANNER 100 | 101 | def options(opt) 102 | opt.on :c, :condition, 'Change the condition of a breakpoint.', :argument => true, :as => Integer 103 | opt.on :s, :show, 'Show breakpoint details and source.', :argument => true, :as => Integer 104 | opt.on :D, :delete, 'Delete a breakpoint.', :argument => true, :as => Integer 105 | opt.on :d, :disable, 'Disable a breakpoint.', :argument => true, :as => Integer 106 | opt.on :e, :enable, 'Enable a disabled breakpoint.', :argument => true, :as => Integer 107 | opt.on :'disable-all', 'Disable all breakpoints.' 108 | opt.on :'delete-all', 'Delete all breakpoints.' 109 | method_options(opt) 110 | end 111 | 112 | def process 113 | Pry.processor.pry = _pry_ 114 | 115 | { :delete => :delete, 116 | :disable => :disable, 117 | :enable => :enable, 118 | :'disable-all' => :disable_all, 119 | :'delete-all' => :clear 120 | }.each do |action, method| 121 | if opts.present?(action) 122 | Breakpoints.__send__ method, *(method == action ? [opts[action]] : []) 123 | return run 'breakpoints' 124 | end 125 | end 126 | 127 | if opts.present?(:condition) 128 | Breakpoints.change(opts[:condition], args.empty? ? nil : args.join(' ')) 129 | run 'breakpoints' 130 | elsif opts.present?(:show) 131 | print_full_breakpoint Breakpoints.find_by_id(opts[:show]) 132 | elsif args.empty? 133 | run 'breakpoints' 134 | else 135 | new_breakpoint 136 | end 137 | end 138 | 139 | def new_breakpoint 140 | place = args.shift 141 | condition = args.join(' ') if 'if' == args.shift 142 | 143 | file, line = 144 | case place 145 | when /^(\d+)$/ # Line number only 146 | line = $1 147 | unless PryDebugger.check_file_context(target) 148 | raise ArgumentError, 'Line number declaration valid only in a file context.' 149 | end 150 | [target.eval('__FILE__'), line] 151 | when /^(.+):(\d+)$/ # File and line number 152 | [$1, $2] 153 | else # Method or class name 154 | self.args = [place] 155 | method_object.source_location 156 | end 157 | 158 | print_full_breakpoint Breakpoints.add(file, line.to_i, condition) 159 | end 160 | end 161 | alias_command 'breakpoint', 'break' 162 | 163 | 164 | create_command 'breakpoints' do 165 | description 'List defined breakpoints.' 166 | 167 | banner <<-BANNER 168 | Usage: breakpoints [OPTIONS] 169 | Aliases: breaks 170 | 171 | List registered breakpoints and their current status. 172 | BANNER 173 | 174 | def options(opt) 175 | opt.on :v, :verbose, 'Print source around each breakpoint.' 176 | end 177 | 178 | def process 179 | if Breakpoints.count > 0 180 | if opts.verbose? # Long-form with source output 181 | Breakpoints.each { |b| print_full_breakpoint(b) } 182 | else # Simple table output 183 | max_width = [Math.log10(Breakpoints.count).ceil, 1].max 184 | header = "#{' ' * (max_width - 1)}# Enabled At " 185 | 186 | output.puts 187 | output.puts text.bold(header) 188 | output.puts text.bold('-' * header.size) 189 | Breakpoints.each do |breakpoint| 190 | output.printf "%#{max_width}d ", breakpoint.id 191 | output.print breakpoint.enabled? ? 'Yes ' : 'No ' 192 | output.print "#{breakpoint.source}:#{breakpoint.pos}" 193 | output.print " (if #{breakpoint.expr})" if breakpoint.expr 194 | output.puts 195 | end 196 | output.puts 197 | end 198 | else 199 | output.puts text.bold('No breakpoints defined.') 200 | end 201 | end 202 | end 203 | alias_command 'breaks', 'breakpoints' 204 | 205 | 206 | helpers do 207 | def breakout_navigation(action, times = nil) 208 | _pry_.binding_stack.clear # Clear the binding stack. 209 | throw :breakout_nav, { # Break out of the REPL loop and 210 | :action => action, # signal the tracer. 211 | :times => times, 212 | :pry => _pry_ 213 | } 214 | end 215 | 216 | # Ensures that a command is executed in a local file context. 217 | def check_file_context 218 | unless PryDebugger.check_file_context(target) 219 | raise Pry::CommandError, 'Cannot find local context. Did you use `binding.pry`?' 220 | end 221 | end 222 | 223 | # Print out full information about a breakpoint including surrounding code 224 | # at that point. 225 | def print_full_breakpoint(breakpoint) 226 | line = breakpoint.pos 227 | output.print text.bold("Breakpoint #{breakpoint.id}: ") 228 | output.print "#{breakpoint.source} @ line #{line} " 229 | output.print breakpoint.enabled? ? '(Enabled)' : '(Disabled)' 230 | output.puts ' :' 231 | if (expr = breakpoint.expr) 232 | output.puts "#{text.bold('Condition:')} #{expr}" 233 | end 234 | output.puts 235 | output.puts Pry::Code.from_file(breakpoint.source). 236 | around(line, 3). 237 | with_line_numbers. 238 | with_marker(line).to_s 239 | output.puts 240 | end 241 | end 242 | end 243 | end 244 | 245 | Pry.commands.import PryDebugger::Commands 246 | -------------------------------------------------------------------------------- /lib/pry-debugger/processor.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'debugger' 3 | 4 | module PryDebugger 5 | class Processor 6 | attr_accessor :pry 7 | 8 | def initialize 9 | Debugger.handler = self 10 | @always_enabled = false 11 | @delayed = Hash.new(0) 12 | end 13 | 14 | # Wrap a Pry REPL to catch navigational commands and act on them. 15 | def run(initial = true, &block) 16 | return_value = nil 17 | command = catch(:breakout_nav) do # Throws from PryDebugger::Commands 18 | return_value = yield 19 | {} # Nothing thrown == no navigational command 20 | end 21 | 22 | times = (command[:times] || 1).to_i # Command argument 23 | times = 1 if times <= 0 24 | 25 | if [:step, :next, :finish].include? command[:action] 26 | @pry = command[:pry] # Pry instance to resume after stepping 27 | Debugger.start unless Debugger.started? 28 | 29 | if initial 30 | # Movement when on the initial binding.pry line will have a frame 31 | # inside Debugger. If we step normally, it'll stop inside this 32 | # Processor. So jump out and stop at the above frame, then step/next 33 | # from our callback. 34 | Debugger.current_context.stop_frame = 1 35 | @delayed[command[:action]] = times 36 | 37 | elsif :next == command[:action] 38 | step_over times 39 | 40 | elsif :step == command[:action] 41 | step times 42 | 43 | elsif :finish == command[:action] 44 | finish 45 | end 46 | else 47 | stop 48 | end 49 | 50 | return_value 51 | end 52 | 53 | # Adjust debugging. When set to false, the Processor will manage enabling 54 | # and disabling the debugger itself. When set to true, the debugger is 55 | # always enabled. 56 | def debugging=(enabled) 57 | if enabled 58 | @always_enabled = true 59 | Debugger.start unless Debugger.started? 60 | else 61 | @always_enabled = false 62 | # Debugger will get stopped if necessary in `stop` once the repl ends. 63 | end 64 | end 65 | 66 | 67 | # --- Callbacks from debugger C extension --- 68 | 69 | def at_line(context, file, line) 70 | return if file && TRACE_IGNORE_FILES.include?(File.expand_path(file)) 71 | 72 | # If stopped for a breakpoint or catchpoint, can't play any delayed steps 73 | # as they'll move away from the interruption point. (Unsure if scenario is 74 | # possible, but just keeping assertions in check.) 75 | @delayed = Hash.new(0) unless :step == context.stop_reason 76 | 77 | if @delayed[:next] > 1 # If any delayed nexts/steps, do 'em. 78 | step_over @delayed[:next] - 1 79 | @delayed = Hash.new(0) 80 | 81 | elsif @delayed[:step] > 1 82 | step @delayed[:step] - 1 83 | @delayed = Hash.new(0) 84 | 85 | elsif @delayed[:finish] > 0 86 | finish 87 | @delayed = Hash.new(0) 88 | 89 | else # Otherwise, resume the pry session at the stopped line. 90 | resume_pry context 91 | end 92 | end 93 | 94 | # Called when a breakpoint is triggered. Note: `at_line`` is called 95 | # immediately after with the context's `stop_reason == :breakpoint`. 96 | def at_breakpoint(context, breakpoint) 97 | @pry.output.print Pry::Helpers::Text.bold("\nBreakpoint #{breakpoint.id}. ") 98 | @pry.output.puts (breakpoint.hit_count == 1 ? 99 | 'First hit.' : 100 | "Hit #{breakpoint.hit_count} times." ) 101 | if (expr = breakpoint.expr) 102 | @pry.output.print Pry::Helpers::Text.bold("Condition: ") 103 | @pry.output.puts expr 104 | end 105 | end 106 | 107 | def at_catchpoint(context, exception) 108 | # TODO 109 | end 110 | 111 | 112 | private 113 | 114 | # Resume an existing Pry REPL at the paused point. Binding extracted from 115 | # the Debugger::Context. 116 | def resume_pry(context) 117 | new_binding = context.frame_binding(0) 118 | Debugger.stop unless @always_enabled 119 | 120 | @pry.binding_stack.clear 121 | run(false) do 122 | @pry.repl new_binding 123 | end 124 | end 125 | 126 | # Move execution forward. 127 | def step(times) 128 | Debugger.current_context.step(times) 129 | end 130 | 131 | # Move execution forward a number of lines in the same frame. 132 | def step_over(lines) 133 | Debugger.current_context.step_over(lines, 0) 134 | end 135 | 136 | # Execute until current frame returns. 137 | def finish 138 | Debugger.current_context.stop_frame = 0 139 | end 140 | 141 | # Cleanup when debugging is stopped and execution continues. 142 | def stop 143 | Debugger.stop if !@always_enabled && Debugger.started? 144 | if PryDebugger.current_remote_server # Cleanup DRb remote if running 145 | PryDebugger.current_remote_server.teardown 146 | end 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /lib/pry-debugger/pry_ext.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | require 'pry-debugger/processor' 3 | 4 | class << Pry 5 | alias_method :start_without_pry_debugger, :start 6 | attr_reader :processor 7 | 8 | def start_with_pry_debugger(target = TOPLEVEL_BINDING, options = {}) 9 | @processor ||= PryDebugger::Processor.new 10 | 11 | if target.is_a?(Binding) && PryDebugger.check_file_context(target) 12 | # Wrap the processer around the usual Pry.start to catch navigation 13 | # commands. 14 | @processor.run(true) do 15 | start_without_pry_debugger(target, options) 16 | end 17 | else 18 | # No need for the tracer unless we have a file context to step through 19 | start_without_pry_debugger(target, options) 20 | end 21 | end 22 | alias_method :start, :start_with_pry_debugger 23 | end 24 | -------------------------------------------------------------------------------- /lib/pry-debugger/pry_remote_ext.rb: -------------------------------------------------------------------------------- 1 | require 'pry-remote' 2 | 3 | module PryRemote 4 | class Server 5 | # Override the call to Pry.start to save off current Server, and not 6 | # teardown the server right after Pry.start finishes. 7 | def run 8 | if PryDebugger.current_remote_server 9 | raise 'Already running a pry-remote session!' 10 | else 11 | PryDebugger.current_remote_server = self 12 | end 13 | 14 | setup 15 | Pry.start @object, { 16 | :input => client.input_proxy, 17 | :output => client.output 18 | } 19 | end 20 | 21 | # Override to reset our saved global current server session. 22 | alias_method :teardown_without_pry_debugger, :teardown 23 | def teardown_with_pry_debugger 24 | return if @torn 25 | 26 | teardown_without_pry_debugger 27 | PryDebugger.current_remote_server = nil 28 | @torn = true 29 | end 30 | alias_method :teardown, :teardown_with_pry_debugger 31 | end 32 | end 33 | 34 | # Ensure cleanup when a program finishes without another break. For example, 35 | # 'next' on the last line of a program won't hit PryDebugger::Processor#run, 36 | # which normally handles cleanup. 37 | at_exit do 38 | if PryDebugger.current_remote_server 39 | PryDebugger.current_remote_server.teardown 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/pry-debugger/version.rb: -------------------------------------------------------------------------------- 1 | module PryDebugger 2 | VERSION = '0.2.3' 3 | end 4 | -------------------------------------------------------------------------------- /pry-debugger.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require File.expand_path('../lib/pry-debugger/version', __FILE__) 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = 'pry-debugger' 7 | gem.version = PryDebugger::VERSION 8 | gem.author = 'Gopal Patel' 9 | gem.email = 'nixme@stillhope.com' 10 | gem.license = 'MIT' 11 | gem.homepage = 'https://github.com/nixme/pry-debugger' 12 | gem.summary = 'Fast debugging with Pry.' 13 | gem.description = "Combine 'pry' with 'debugger'. Adds 'step', 'next', and 'continue' commands to control execution." 14 | 15 | gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 16 | gem.files = `git ls-files`.split("\n") 17 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | gem.require_paths = ["lib"] 19 | 20 | # Dependencies 21 | gem.required_ruby_version = '>= 1.9.2' 22 | gem.add_runtime_dependency 'pry', '>= 0.9.10', '< 0.11.0' 23 | gem.add_runtime_dependency 'debugger', '~> 1.3' 24 | gem.add_development_dependency 'pry-remote', '~> 0.1.6' 25 | end 26 | --------------------------------------------------------------------------------