summaryrefslogtreecommitdiff
path: root/lib/pry/prompt.rb
blob: 906a5f01b33dc381000eb1dbd56938977b04e0bf (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
class Pry
  # Prompt represents the Pry prompt, which can be used with Readline-like
  # libraries. It defines a few default prompts (default prompt, simple prompt,
  # etc) and also provides an API for adding and implementing custom prompts.
  #
  # @example Registering a new Pry prompt
  #   Pry::Prompt.add(
  #     :ipython,
  #     'IPython-like prompt', [':', '...:']
  #   ) do |_context, _nesting, pry_instance, sep|
  #     sep == ':' ? "In [#{pry_instance.input_ring.count}]: " : '   ...: '
  #   end
  #
  #   # Produces:
  #   # In [3]: def foo
  #   #    ...:   puts 'foo'
  #   #    ...: end
  #   # => :foo
  #   # In [4]:
  #
  # @example Manually instantiating the Prompt class
  #   prompt_procs = [
  #     proc { '#{rand(1)}>" },
  #     proc { "#{('a'..'z').to_a.sample}*" }
  #   ]
  #   prompt = Pry::Prompt.new(
  #     :random,
  #     'Random number or letter prompt.',
  #     prompt_procs
  #   )
  #   prompt.wait_proc.call(...) #=>
  #   prompt.incomplete_proc.call(...)
  #
  # @since v0.11.0
  # @api public
  class Prompt
    # A Hash that holds all prompts. The keys of the Hash are prompt
    # names, the values are Hash instances of the format {:description, :value}.
    @prompts = {}

    class << self
      # Retrieves a prompt.
      #
      # @example
      #   Prompt[:my_prompt]
      #
      # @param [Symbol] name The name of the prompt you want to access
      # @return [Hash{Symbol=>Object}]
      # @since v0.12.0
      def [](name)
        @prompts[name.to_s]
      end

      # @return [Hash{Symbol=>Hash}] the duplicate of the internal prompts hash
      # @note Use this for read-only operations
      # @since v0.12.0
      def all
        @prompts.dup
      end

      # Adds a new prompt to the prompt hash.
      #
      # @param [Symbol] name
      # @param [String] description
      # @param [Array<String>] separators The separators to differentiate
      #   between prompt modes (default mode and class/method definition mode).
      #   The Array *must* have a size of 2.
      # @yield [context, nesting, pry_instance, sep]
      # @yieldparam context [Object] the context where Pry is currently in
      # @yieldparam nesting [Integer] whether the context is nested
      # @yieldparam pry_instance [Pry] the Pry instance
      # @yieldparam separator [String] separator string
      # @return [nil]
      # @raise [ArgumentError] if the size of `separators` is not 2
      # @raise [ArgumentError] if `prompt_name` is already occupied
      # @since v0.12.0
      def add(name, description = '', separators = %w[> *])
        name = name.to_s

        unless separators.size == 2
          raise ArgumentError, "separators size must be 2, given #{separators.size}"
        end

        if @prompts.key?(name)
          raise ArgumentError, "the '#{name}' prompt was already added"
        end

        @prompts[name] = new(
          name,
          description,
          separators.map do |sep|
            proc do |context, nesting, pry_instance|
              yield(context, nesting, pry_instance, sep)
            end
          end
        )

        nil
      end
    end

    # @return [String]
    attr_reader :name

    # @return [String]
    attr_reader :description

    # @return [Array<Proc>] the array of procs that hold
    #   `[wait_proc, incomplete_proc]`
    attr_reader :prompt_procs

    # @param [String] name
    # @param [String] description
    # @param [Array<Proc>] prompt_procs
    def initialize(name, description, prompt_procs)
      @name = name
      @description = description
      @prompt_procs = prompt_procs
    end

    # @return [Proc] the proc which builds the wait prompt (`>`)
    def wait_proc
      @prompt_procs.first
    end

    # @return [Proc] the proc which builds the prompt when in the middle of an
    #   expression such as open method, etc. (`*`)
    def incomplete_proc
      @prompt_procs.last
    end

    # @deprecated Use a `Pry::Prompt` instance directly
    def [](key)
      key = key.to_s
      loc = caller_locations(1..1).first

      if %w[name description].include?(key)
        warn(
          "#{loc.path}:#{loc.lineno}: warning: `Pry::Prompt[:#{@name}][:#{key}]` " \
          "is deprecated. Use `#{self.class}##{key}` instead"
        )
        public_send(key)
      elsif key.to_s == 'value'
        warn(
          "#{loc.path}:#{loc.lineno}: warning: `#{self.class}[:#{@name}][:value]` " \
          "is deprecated. Use `#{self.class}#prompt_procs` instead or an " \
          "instance of `#{self.class}` directly"
        )
        @prompt_procs
      end
    end

    add(
      :default,
      "The default Pry prompt. Includes information about the current expression \n" \
      "number, evaluation context, and nesting level, plus a reminder that you're \n" \
      'using Pry.'
    ) do |context, nesting, pry_instance, sep|
      format(
        "[%<in_count>s] %<name>s(%<context>s)%<nesting>s%<separator>s ",
        in_count: pry_instance.input_ring.count,
        name: pry_instance.config.prompt_name,
        context: Pry.view_clip(context),
        nesting: (nesting > 0 ? ":#{nesting}" : ''),
        separator: sep
      )
    end

    add(
      :simple,
      "A simple `>>`.",
      ['>> ', ' | ']
    ) do |_, _, _, sep|
      sep
    end

    add(
      :nav,
      "A prompt that displays the binding stack as a path and includes information \n" \
      "about #{Helpers::Text.bold('_in_')} and #{Helpers::Text.bold('_out_')}.",
      %w[> *]
    ) do |_context, _nesting, pry_instance, sep|
      tree = pry_instance.binding_stack.map { |b| Pry.view_clip(b.eval('self')) }
      format(
        "[%<in_count>s] (%<name>s) %<tree>s: %<stack_size>s%<separator>s ",
        in_count: pry_instance.input_ring.count,
        name: pry_instance.config.prompt_name,
        tree: tree.join(' / '),
        stack_size: pry_instance.binding_stack.size - 1,
        separator: sep
      )
    end

    add(
      :shell,
      'A prompt that displays `$PWD` as you change it.',
      %w[$ *]
    ) do |context, _nesting, pry_instance, sep|
      format(
        "%<name>s %<context>s:%<pwd>s %<separator>s ",
        name: pry_instance.config.prompt_name,
        context: Pry.view_clip(context),
        pwd: Dir.pwd,
        separator: sep
      )
    end

    add(
      :none,
      'Wave goodbye to the Pry prompt.',
      Array.new(2)
    ) { '' }
  end
end