summaryrefslogtreecommitdiff
path: root/lib/pry/repl.rb
blob: 8a0e95db124b389180f6f09cbf433cbbfdf1f1cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
require 'forwardable'

class Pry
  class REPL
    extend Forwardable
    def_delegators :@pry, :input, :output

    # @return [Pry] The instance of {Pry} that the user is controlling.
    attr_accessor :pry

    # Instantiate a new {Pry} instance with the given options, then start a
    # {REPL} instance wrapping it.
    # @option options See {Pry#initialize}
    def self.start(options)
      new(Pry.new(options)).start
    end

    # Create an instance of {REPL} wrapping the given {Pry}.
    # @param [Pry] pry The instance of {Pry} that this {REPL} will control.
    # @param [Hash] options Options for this {REPL} instance.
    # @option options [Object] :target The initial target of the session.
    def initialize(pry, options = {})
      @pry    = pry
      @indent = Pry::Indent.new

      if options[:target]
        @pry.push_binding options[:target]
      end
    end

    # Start the read-eval-print loop.
    # @return [Object?] If the session throws `:breakout`, return the value
    #   thrown with it.
    # @raise [Exception] If the session throws `:raise_up`, raise the exception
    #   thrown with it.
    def start
      prologue
      repl
    ensure
      epilogue
    end

    private

    # Set up the repl session.
    # @return [void]
    def prologue
      pry.exec_hook :before_session, pry.output, pry.current_binding, pry

      # Clear the line before starting Pry. This fixes issue #566.
      if Pry.config.correct_indent
        Kernel.print Pry::Helpers::BaseHelpers.windows_ansi? ? "\e[0F" : "\e[0G"
      end
    end

    # The actual read-eval-print loop.
    #
    # The {REPL} instance is responsible for reading and looping, whereas the
    # {Pry} instance is responsible for evaluating user input and printing
    # return values and command output.
    #
    # @return [Object?] If the session throws `:breakout`, return the value
    #   thrown with it.
    # @raise [Exception] If the session throws `:raise_up`, raise the exception
    #   thrown with it.
    def repl
      loop do
        case line = read(pry.select_prompt)
        when :control_c
          advance_cursor
          pry.reset_eval_string
        when :no_more_input
          advance_cursor
          break
        else
          advance_cursor if line.nil?
          return pry.exit_value unless pry.eval(line)
        end
      end
    end

    # Print an empty line if the output is a TTY.
    def advance_cursor
      output.puts "" if output.tty?
    end

    # Clean up after the repl session.
    # @return [void]
    def epilogue
      pry.exec_hook :after_session, pry.output, pry.current_binding, pry
    end

    # Return the next line of input to be sent to the {Pry} instance.
    # @param [String] prompt The prompt to use for input.
    # @return [String] The line entered by the user.
    # @return [nil] On `<Ctrl-D>`.
    # @return [:control_c] On `<Ctrl+C>`.
    # @return [:no_more_input] On EOF.
    def read(prompt)
      set_completion_proc

      if input == Readline
        input.readline(prompt, false) # false since we'll add it manually
      elsif input.method(:readline).arity == 1
        input.readline(prompt)
      else
        input.readline
      end
    end

    private

    # Set the default completion proc, if applicable.
    def set_completion_proc
      if input.respond_to? :completion_proc=
        input.completion_proc = proc do |input|
          @pry.complete input
        end
      end
    end

    # Manage switching of input objects on encountering `EOFError`s.
    # @return [Object] Whatever the given block returns.
    # @return [:no_more_input] Indicates that no more input can be read.
    def read_with_handle_eof(prompt)
      should_retry = true

      begin
        read_without_handle_eof(prompt)
      rescue EOFError
        pry.input = Pry.config.input

        if should_retry
          should_retry = false
          retry
        else
          output.puts "Error: Pry ran out of things to read from! " \
            "Attempting to break out of REPL."
          return :no_more_input
        end
      end
    end
    alias_method :read_without_handle_eof, :read
    alias_method :read, :read_with_handle_eof

    # Handle `Ctrl-C` like Bash: empty the current input buffer, but don't
    # quit.  This is only for MRI 1.9; other versions of Ruby don't let you
    # send Interrupt from within Readline.
    # @return [Object] Whatever the given block returns.
    # @return [:control_c] Indicates that the user hit `Ctrl-C`.
    def read_with_handle_interrupt(prompt)
      read_without_handle_interrupt(prompt)
    rescue Interrupt
      return :control_c
    end
    alias_method :read_without_handle_interrupt, :read
    alias_method :read, :read_with_handle_interrupt

    # Deal with any random errors that happen while trying to get user input.
    # @return [Object] Whatever the given block returns.
    # @return [:no_more_input] Indicates that no more input can be read.
    def read_with_handle_read_errors(prompt)
      exception_count = 0

      begin
        read_without_handle_read_errors(prompt)
      # If we get a random error when trying to read a line we don't want to
      # automatically retry, as the user will see a lot of error messages
      # scroll past and be unable to do 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"
        puts "  Pry.config.output = STDOUT"
        puts "  binding.pry"
        return :no_more_input
      end
    end
    alias_method :read_without_handle_read_errors, :read
    alias_method :read, :read_with_handle_read_errors

    def read_with_fix_indentation(prompt)
      @indent.reset if pry.eval_string.empty?

      indentation = Pry.config.auto_indent ? @indent.current_prefix : ''

      line = read_without_fix_indentation("#{prompt}#{indentation}")

      if line.is_a? String
        fix_indentation(line, indentation, prompt)
      else
        # nil for EOF, :no_more_input for error, or :control_c for <Ctrl-C>
        line
      end
    end
    alias_method :read_without_fix_indentation, :read
    alias_method :read, :read_with_fix_indentation

    def fix_indentation(line, indentation, prompt)
      if Pry.config.auto_indent
        original_line = "#{indentation}#{line}"
        indented_line = @indent.indent(line)

        if output.tty? && @indent.should_correct_indentation?
          output.print @indent.correct_indentation(
            prompt, indented_line,
            original_line.length - indented_line.length
          )
          output.flush
        end

        indented_line
      else
        line
      end
    end
  end
end