pry 0.10.0.pre4-x64-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +702 -0
  3. data/LICENSE +25 -0
  4. data/README.md +406 -0
  5. data/bin/pry +16 -0
  6. data/lib/pry.rb +161 -0
  7. data/lib/pry/cli.rb +220 -0
  8. data/lib/pry/code.rb +341 -0
  9. data/lib/pry/code/code_file.rb +103 -0
  10. data/lib/pry/code/code_range.rb +71 -0
  11. data/lib/pry/code/loc.rb +92 -0
  12. data/lib/pry/code_object.rb +172 -0
  13. data/lib/pry/color_printer.rb +55 -0
  14. data/lib/pry/command.rb +692 -0
  15. data/lib/pry/command_set.rb +443 -0
  16. data/lib/pry/commands.rb +6 -0
  17. data/lib/pry/commands/amend_line.rb +99 -0
  18. data/lib/pry/commands/bang.rb +20 -0
  19. data/lib/pry/commands/bang_pry.rb +17 -0
  20. data/lib/pry/commands/cat.rb +62 -0
  21. data/lib/pry/commands/cat/abstract_formatter.rb +27 -0
  22. data/lib/pry/commands/cat/exception_formatter.rb +77 -0
  23. data/lib/pry/commands/cat/file_formatter.rb +67 -0
  24. data/lib/pry/commands/cat/input_expression_formatter.rb +43 -0
  25. data/lib/pry/commands/cd.rb +41 -0
  26. data/lib/pry/commands/change_inspector.rb +27 -0
  27. data/lib/pry/commands/change_prompt.rb +26 -0
  28. data/lib/pry/commands/code_collector.rb +165 -0
  29. data/lib/pry/commands/disable_pry.rb +27 -0
  30. data/lib/pry/commands/disabled_commands.rb +2 -0
  31. data/lib/pry/commands/easter_eggs.rb +112 -0
  32. data/lib/pry/commands/edit.rb +195 -0
  33. data/lib/pry/commands/edit/exception_patcher.rb +25 -0
  34. data/lib/pry/commands/edit/file_and_line_locator.rb +36 -0
  35. data/lib/pry/commands/exit.rb +42 -0
  36. data/lib/pry/commands/exit_all.rb +29 -0
  37. data/lib/pry/commands/exit_program.rb +23 -0
  38. data/lib/pry/commands/find_method.rb +193 -0
  39. data/lib/pry/commands/fix_indent.rb +19 -0
  40. data/lib/pry/commands/gem_cd.rb +26 -0
  41. data/lib/pry/commands/gem_install.rb +32 -0
  42. data/lib/pry/commands/gem_list.rb +33 -0
  43. data/lib/pry/commands/gem_open.rb +29 -0
  44. data/lib/pry/commands/gist.rb +101 -0
  45. data/lib/pry/commands/help.rb +164 -0
  46. data/lib/pry/commands/hist.rb +180 -0
  47. data/lib/pry/commands/import_set.rb +22 -0
  48. data/lib/pry/commands/install_command.rb +53 -0
  49. data/lib/pry/commands/jump_to.rb +29 -0
  50. data/lib/pry/commands/list_inspectors.rb +35 -0
  51. data/lib/pry/commands/list_prompts.rb +35 -0
  52. data/lib/pry/commands/ls.rb +114 -0
  53. data/lib/pry/commands/ls/constants.rb +47 -0
  54. data/lib/pry/commands/ls/formatter.rb +49 -0
  55. data/lib/pry/commands/ls/globals.rb +48 -0
  56. data/lib/pry/commands/ls/grep.rb +21 -0
  57. data/lib/pry/commands/ls/instance_vars.rb +39 -0
  58. data/lib/pry/commands/ls/interrogatable.rb +18 -0
  59. data/lib/pry/commands/ls/jruby_hacks.rb +49 -0
  60. data/lib/pry/commands/ls/local_names.rb +35 -0
  61. data/lib/pry/commands/ls/local_vars.rb +39 -0
  62. data/lib/pry/commands/ls/ls_entity.rb +70 -0
  63. data/lib/pry/commands/ls/methods.rb +57 -0
  64. data/lib/pry/commands/ls/methods_helper.rb +46 -0
  65. data/lib/pry/commands/ls/self_methods.rb +32 -0
  66. data/lib/pry/commands/nesting.rb +25 -0
  67. data/lib/pry/commands/play.rb +103 -0
  68. data/lib/pry/commands/pry_backtrace.rb +25 -0
  69. data/lib/pry/commands/pry_version.rb +17 -0
  70. data/lib/pry/commands/raise_up.rb +32 -0
  71. data/lib/pry/commands/reload_code.rb +62 -0
  72. data/lib/pry/commands/reset.rb +18 -0
  73. data/lib/pry/commands/ri.rb +60 -0
  74. data/lib/pry/commands/save_file.rb +61 -0
  75. data/lib/pry/commands/shell_command.rb +48 -0
  76. data/lib/pry/commands/shell_mode.rb +25 -0
  77. data/lib/pry/commands/show_doc.rb +83 -0
  78. data/lib/pry/commands/show_info.rb +195 -0
  79. data/lib/pry/commands/show_input.rb +17 -0
  80. data/lib/pry/commands/show_source.rb +50 -0
  81. data/lib/pry/commands/simple_prompt.rb +22 -0
  82. data/lib/pry/commands/stat.rb +40 -0
  83. data/lib/pry/commands/switch_to.rb +23 -0
  84. data/lib/pry/commands/toggle_color.rb +24 -0
  85. data/lib/pry/commands/watch_expression.rb +105 -0
  86. data/lib/pry/commands/watch_expression/expression.rb +38 -0
  87. data/lib/pry/commands/whereami.rb +190 -0
  88. data/lib/pry/commands/wtf.rb +57 -0
  89. data/lib/pry/config.rb +24 -0
  90. data/lib/pry/config/behavior.rb +139 -0
  91. data/lib/pry/config/convenience.rb +26 -0
  92. data/lib/pry/config/default.rb +165 -0
  93. data/lib/pry/core_extensions.rb +131 -0
  94. data/lib/pry/editor.rb +133 -0
  95. data/lib/pry/exceptions.rb +77 -0
  96. data/lib/pry/helpers.rb +5 -0
  97. data/lib/pry/helpers/base_helpers.rb +113 -0
  98. data/lib/pry/helpers/command_helpers.rb +156 -0
  99. data/lib/pry/helpers/documentation_helpers.rb +75 -0
  100. data/lib/pry/helpers/options_helpers.rb +27 -0
  101. data/lib/pry/helpers/table.rb +109 -0
  102. data/lib/pry/helpers/text.rb +107 -0
  103. data/lib/pry/history.rb +125 -0
  104. data/lib/pry/history_array.rb +121 -0
  105. data/lib/pry/hooks.rb +230 -0
  106. data/lib/pry/indent.rb +406 -0
  107. data/lib/pry/input_completer.rb +242 -0
  108. data/lib/pry/input_lock.rb +132 -0
  109. data/lib/pry/inspector.rb +27 -0
  110. data/lib/pry/last_exception.rb +61 -0
  111. data/lib/pry/method.rb +546 -0
  112. data/lib/pry/method/disowned.rb +53 -0
  113. data/lib/pry/method/patcher.rb +125 -0
  114. data/lib/pry/method/weird_method_locator.rb +186 -0
  115. data/lib/pry/module_candidate.rb +136 -0
  116. data/lib/pry/object_path.rb +82 -0
  117. data/lib/pry/output.rb +50 -0
  118. data/lib/pry/pager.rb +234 -0
  119. data/lib/pry/plugins.rb +103 -0
  120. data/lib/pry/prompt.rb +26 -0
  121. data/lib/pry/pry_class.rb +375 -0
  122. data/lib/pry/pry_instance.rb +654 -0
  123. data/lib/pry/rbx_path.rb +22 -0
  124. data/lib/pry/repl.rb +202 -0
  125. data/lib/pry/repl_file_loader.rb +74 -0
  126. data/lib/pry/rubygem.rb +82 -0
  127. data/lib/pry/terminal.rb +79 -0
  128. data/lib/pry/test/helper.rb +170 -0
  129. data/lib/pry/version.rb +3 -0
  130. data/lib/pry/wrapped_module.rb +373 -0
  131. metadata +248 -0
@@ -0,0 +1,230 @@
1
+ class Pry
2
+
3
+ # Implements a hooks system for Pry. A hook is a callable that is
4
+ # associated with an event. A number of events are currently
5
+ # provided by Pry, these include: `:when_started`, `:before_session`, `:after_session`.
6
+ # A hook must have a name, and is connected with an event by the
7
+ # `Pry::Hooks#add_hook` method.
8
+ # @example Adding a hook for the `:before_session` event.
9
+ # Pry.config.hooks.add_hook(:before_session, :say_hi) do
10
+ # puts "hello"
11
+ # end
12
+ class Hooks
13
+
14
+ # Converts a hash to a `Pry::Hooks` instance. All hooks defined
15
+ # this way are anonymous. This functionality is primarily to
16
+ # provide backwards-compatibility with the old hash-based hook
17
+ # system in Pry versions < 0.9.8
18
+ # @param [Hash] hash The hash to convert to `Pry::Hooks`.
19
+ # @return [Pry::Hooks] The resulting `Pry::Hooks` instance.
20
+ def self.from_hash(hash)
21
+ return hash if hash.instance_of?(self)
22
+ instance = new
23
+ hash.each do |k, v|
24
+ instance.add_hook(k, nil, v)
25
+ end
26
+ instance
27
+ end
28
+
29
+ def initialize
30
+ @hooks = {}
31
+ end
32
+
33
+ # Ensure that duplicates have their @hooks object
34
+ def initialize_copy(orig)
35
+ hooks_dup = @hooks.dup
36
+ @hooks.each do |k, v|
37
+ hooks_dup[k] = v.dup
38
+ end
39
+
40
+ @hooks = hooks_dup
41
+ end
42
+
43
+ def hooks
44
+ @hooks
45
+ end
46
+ protected :hooks
47
+
48
+ def errors
49
+ @errors ||= []
50
+ end
51
+
52
+ # Destructively merge the contents of two `Pry:Hooks` instances.
53
+ # @param [Pry::Hooks] other The `Pry::Hooks` instance to merge
54
+ # @return [Pry:Hooks] Returns the receiver.
55
+ # @example
56
+ # hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
57
+ # Pry::Hooks.new.merge!(hooks)
58
+ def merge!(other)
59
+ @hooks.merge!(other.dup.hooks) do |key, v1, v2|
60
+ merge_arrays(v1, v2)
61
+ end
62
+
63
+ self
64
+ end
65
+
66
+ def merge_arrays(array1, array2)
67
+ uniq_keeping_last(array1 + array2, &:first)
68
+ end
69
+ private :merge_arrays
70
+
71
+ def uniq_keeping_last(input, &block)
72
+ hash, output = {}, []
73
+ input.reverse.each{ |i| hash[block[i]] ||= (output.unshift i) }
74
+ output
75
+ end
76
+ private :uniq_keeping_last
77
+
78
+ # Return a new `Pry::Hooks` instance containing a merge of the contents of two `Pry:Hooks` instances,
79
+ # @param [Pry::Hooks] other The `Pry::Hooks` instance to merge
80
+ # @return [Pry::Hooks] The new hash.
81
+ # @example
82
+ # hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
83
+ # Pry::Hooks.new.merge(hooks)
84
+ def merge(other)
85
+ self.dup.tap do |v|
86
+ v.merge!(other)
87
+ end
88
+ end
89
+
90
+ # Add a new hook to be executed for the `name` even.
91
+ # @param [Symbol] event_name The name of the event.
92
+ # @param [Symbol] hook_name The name of the hook.
93
+ # @param [#call] callable The callable.
94
+ # @yield The block to use as the callable (if `callable` parameter not provided)
95
+ # @return [Pry:Hooks] Returns the receiver.
96
+ # @example
97
+ # Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
98
+ def add_hook(event_name, hook_name, callable=nil, &block)
99
+ @hooks[event_name] ||= []
100
+
101
+ # do not allow duplicates, but allow multiple `nil` hooks
102
+ # (anonymous hooks)
103
+ if hook_exists?(event_name, hook_name) && !hook_name.nil?
104
+ raise ArgumentError, "Hook with name '#{hook_name}' already defined!"
105
+ end
106
+
107
+ if !block && !callable
108
+ raise ArgumentError, "Must provide a block or callable."
109
+ end
110
+
111
+ # ensure we only have one anonymous hook
112
+ @hooks[event_name].delete_if { |h, k| h.nil? } if hook_name.nil?
113
+
114
+ if block
115
+ @hooks[event_name] << [hook_name, block]
116
+ elsif callable
117
+ @hooks[event_name] << [hook_name, callable]
118
+ end
119
+
120
+ self
121
+ end
122
+
123
+ # Execute the list of hooks for the `event_name` event.
124
+ # @param [Symbol] event_name The name of the event.
125
+ # @param [Array] args The arguments to pass to each hook function.
126
+ # @return [Object] The return value of the last executed hook.
127
+ # @example
128
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
129
+ # my_hooks.exec_hook(:before_session) #=> OUTPUT: "hi!"
130
+ def exec_hook(event_name, *args, &block)
131
+ @hooks[event_name] ||= []
132
+
133
+ @hooks[event_name].map do |hook_name, callable|
134
+ begin
135
+ callable.call(*args, &block)
136
+ rescue RescuableException => e
137
+ errors << e
138
+ e
139
+ end
140
+ end.last
141
+ end
142
+
143
+ # Return the number of hook functions registered for the `event_name` event.
144
+ # @param [Symbol] event_name The name of the event.
145
+ # @return [Fixnum] The number of hook functions for `event_name`.
146
+ # @example
147
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
148
+ # my_hooks.count(:before_session) #=> 1
149
+ def hook_count(event_name)
150
+ @hooks[event_name] ||= []
151
+ @hooks[event_name].size
152
+ end
153
+
154
+ # Return a specific hook for a given event.
155
+ # @param [Symbol] event_name The name of the event.
156
+ # @param [Symbol] hook_name The name of the hook
157
+ # @return [#call] The requested hook.
158
+ # @example
159
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
160
+ # my_hooks.get_hook(:before_session, :say_hi).call #=> "hi!"
161
+ def get_hook(event_name, hook_name)
162
+ @hooks[event_name] ||= []
163
+ hook = @hooks[event_name].find { |current_hook_name, callable| current_hook_name == hook_name }
164
+ hook.last if hook
165
+ end
166
+
167
+ # Return the hash of hook names / hook functions for a
168
+ # given event. (Note that modifying the returned hash does not
169
+ # alter the hooks, use add_hook/delete_hook for that).
170
+ # @param [Symbol] event_name The name of the event.
171
+ # @return [Hash] The hash of hook names / hook functions.
172
+ # @example
173
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
174
+ # my_hooks.get_hooks(:before_session) #=> {:say_hi=>#<Proc:0x00000101645e18@(pry):9>}
175
+ def get_hooks(event_name)
176
+ @hooks[event_name] ||= []
177
+ Hash[@hooks[event_name]]
178
+ end
179
+
180
+ # Delete a hook for an event.
181
+ # @param [Symbol] event_name The name of the event.
182
+ # @param [Symbol] hook_name The name of the hook.
183
+ # to delete.
184
+ # @return [#call] The deleted hook.
185
+ # @example
186
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
187
+ # my_hooks.delete_hook(:before_session, :say_hi)
188
+ def delete_hook(event_name, hook_name)
189
+ @hooks[event_name] ||= []
190
+ deleted_callable = nil
191
+
192
+ @hooks[event_name].delete_if do |current_hook_name, callable|
193
+ if current_hook_name == hook_name
194
+ deleted_callable = callable
195
+ true
196
+ else
197
+ false
198
+ end
199
+ end
200
+ deleted_callable
201
+ end
202
+
203
+ # Clear all hooks functions for a given event.
204
+ # @param [String] event_name The name of the event.
205
+ # @example
206
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
207
+ # my_hooks.delete_hook(:before_session)
208
+ def delete_hooks(event_name)
209
+ @hooks[event_name] = []
210
+ end
211
+
212
+ alias_method :clear, :delete_hooks
213
+
214
+ # Remove all events and hooks, clearing out the Pry::Hooks
215
+ # instance completely.
216
+ # @example
217
+ # my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
218
+ # my_hooks.clear_all
219
+ def clear_all
220
+ @hooks = {}
221
+ end
222
+
223
+ # @param [Symbol] event_name Name of the event.
224
+ # @param [Symbol] hook_name Name of the hook.
225
+ # @return [Boolean] Whether the hook by the name `hook_name`
226
+ def hook_exists?(event_name, hook_name)
227
+ !!(@hooks[event_name] && @hooks[event_name].find { |name, _| name == hook_name })
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,406 @@
1
+ require 'coderay'
2
+
3
+ class Pry
4
+ ##
5
+ # Pry::Indent is a class that can be used to indent a number of lines
6
+ # containing Ruby code similar as to how IRB does it (but better). The class
7
+ # works by tokenizing a string using CodeRay and then looping over those
8
+ # tokens. Based on the tokens in a line of code that line (or the next one)
9
+ # will be indented or un-indented by correctly.
10
+ #
11
+ class Indent
12
+ include Helpers::BaseHelpers
13
+
14
+ # Raised if {#module_nesting} would not work.
15
+ class UnparseableNestingError < StandardError; end
16
+
17
+ # @return [String] String containing the spaces to be inserted before the next line.
18
+ attr_reader :indent_level
19
+
20
+ # @return [Array<String>] The stack of open tokens.
21
+ attr_reader :stack
22
+
23
+ # The amount of spaces to insert for each indent level.
24
+ SPACES = ' '
25
+
26
+ # Hash containing all the tokens that should increase the indentation
27
+ # level. The keys of this hash are open tokens, the values the matching
28
+ # tokens that should prevent a line from being indented if they appear on
29
+ # the same line.
30
+ OPEN_TOKENS = {
31
+ 'def' => 'end',
32
+ 'class' => 'end',
33
+ 'module' => 'end',
34
+ 'do' => 'end',
35
+ 'if' => 'end',
36
+ 'unless' => 'end',
37
+ 'while' => 'end',
38
+ 'until' => 'end',
39
+ 'for' => 'end',
40
+ 'case' => 'end',
41
+ 'begin' => 'end',
42
+ '[' => ']',
43
+ '{' => '}',
44
+ '(' => ')'
45
+ }
46
+
47
+ # Which tokens can either be open tokens, or appear as modifiers on
48
+ # a single-line.
49
+ SINGLELINE_TOKENS = %w(if while until unless rescue)
50
+
51
+ # Which tokens can be followed by an optional "do" keyword.
52
+ OPTIONAL_DO_TOKENS = %w(for while until)
53
+
54
+ # Collection of token types that should be ignored. Without this list
55
+ # keywords such as "class" inside strings would cause the code to be
56
+ # indented incorrectly.
57
+ #
58
+ # :pre_constant and :preserved_constant are the CodeRay 0.9.8 and 1.0.0
59
+ # classifications of "true", "false", and "nil".
60
+ IGNORE_TOKENS = [:space, :content, :string, :method, :ident,
61
+ :constant, :pre_constant, :predefined_constant]
62
+
63
+ # Tokens that indicate the end of a statement (i.e. that, if they appear
64
+ # directly before an "if" indicates that that if applies to the same line,
65
+ # not the next line)
66
+ #
67
+ # :reserved and :keywords are the CodeRay 0.9.8 and 1.0.0 respectively
68
+ # classifications of "super", "next", "return", etc.
69
+ STATEMENT_END_TOKENS = IGNORE_TOKENS + [:regexp, :integer, :float, :keyword,
70
+ :delimiter, :reserved]
71
+
72
+ # Collection of tokens that should appear dedented even though they
73
+ # don't affect the surrounding code.
74
+ MIDWAY_TOKENS = %w(when else elsif ensure rescue)
75
+
76
+ # Clean the indentation of a fragment of ruby.
77
+ #
78
+ # @param [String] str
79
+ # @return [String]
80
+ def self.indent(str)
81
+ new.indent(str)
82
+ end
83
+
84
+ # Get the module nesting at the given point in the given string.
85
+ #
86
+ # NOTE If the line specified contains a method definition, then the nesting
87
+ # at the start of the method definition is used. Otherwise the nesting from
88
+ # the end of the line is used.
89
+ #
90
+ # @param [String] str The ruby code to analyze
91
+ # @param [Fixnum] line_number The line number (starting from 1)
92
+ # @return [Array<String>]
93
+ def self.nesting_at(str, line_number)
94
+ indent = new
95
+ lines = str.split("\n")
96
+ n = line_number - 1
97
+ to_indent = lines[0...n] << (lines[n] || "").split("def").first(1)
98
+ indent.indent(to_indent.join("\n") << "\n")
99
+ indent.module_nesting
100
+ end
101
+
102
+ def initialize
103
+ reset
104
+ end
105
+
106
+ # reset internal state
107
+ def reset
108
+ @stack = []
109
+ @indent_level = ''
110
+ @heredoc_queue = []
111
+ @close_heredocs = {}
112
+ @string_start = nil
113
+ @awaiting_class = false
114
+ @module_nesting = []
115
+ self
116
+ end
117
+
118
+ # Indents a string and returns it. This string can either be a single line
119
+ # or multiple ones.
120
+ #
121
+ # @example
122
+ # str = <<TXT
123
+ # class User
124
+ # attr_accessor :name
125
+ # end
126
+ # TXT
127
+ #
128
+ # # This would result in the following being displayed:
129
+ # #
130
+ # # class User
131
+ # # attr_accessor :name
132
+ # # end
133
+ # #
134
+ # puts Pry::Indent.new.indent(str)
135
+ #
136
+ # @param [String] input The input string to indent.
137
+ # @return [String] The indented version of +input+.
138
+ #
139
+ def indent(input)
140
+ output = ''
141
+ prefix = indent_level
142
+
143
+ input.lines.each do |line|
144
+
145
+ if in_string?
146
+ tokens = tokenize("#{open_delimiters_line}\n#{line}")
147
+ tokens = tokens.drop_while{ |token, type| !(String === token && token.include?("\n")) }
148
+ previously_in_string = true
149
+ else
150
+ tokens = tokenize(line)
151
+ previously_in_string = false
152
+ end
153
+
154
+ before, after = indentation_delta(tokens)
155
+
156
+ before.times{ prefix.sub! SPACES, '' }
157
+ new_prefix = prefix + SPACES * after
158
+
159
+ line = prefix + line.lstrip unless previously_in_string
160
+
161
+ output += line
162
+
163
+ prefix = new_prefix
164
+ end
165
+
166
+ @indent_level = prefix
167
+
168
+ return output
169
+ end
170
+
171
+ # Get the indentation for the start of the next line.
172
+ #
173
+ # This is what's used between the prompt and the cursor in pry.
174
+ #
175
+ # @return String The correct number of spaces
176
+ #
177
+ def current_prefix
178
+ in_string? ? '' : indent_level
179
+ end
180
+
181
+ # Get the change in indentation indicated by the line.
182
+ #
183
+ # By convention, you remove indent from the line containing end tokens,
184
+ # but add indent to the line *after* that which contains the start tokens.
185
+ #
186
+ # This method returns a pair, where the first number is the number of closings
187
+ # on this line (i.e. the number of indents to remove before the line) and the
188
+ # second is the number of openings (i.e. the number of indents to add after
189
+ # this line)
190
+ #
191
+ # @param [Array] tokens A list of tokens to scan.
192
+ # @return [Array[Integer]]
193
+ #
194
+ def indentation_delta(tokens)
195
+
196
+ # We need to keep track of whether we've seen a "for" on this line because
197
+ # if the line ends with "do" then that "do" should be discounted (i.e. we're
198
+ # only opening one level not two) To do this robustly we want to keep track
199
+ # of the indent level at which we saw the for, so we can differentiate
200
+ # between "for x in [1,2,3] do" and "for x in ([1,2,3].map do" properly
201
+ seen_for_at = []
202
+
203
+ # When deciding whether an "if" token is the start of a multiline statement,
204
+ # or just the middle of a single-line if statement, we just look at the
205
+ # preceding token, which is tracked here.
206
+ last_token, last_kind = [nil, nil]
207
+
208
+ # delta keeps track of the total difference from the start of each line after
209
+ # the given token, 0 is just the level at which the current line started for
210
+ # reference.
211
+ remove_before, add_after = [0, 0]
212
+
213
+ # If the list of tokens contains a matching closing token the line should
214
+ # not be indented (and thus we should return true).
215
+ tokens.each do |token, kind|
216
+ is_singleline_if = (SINGLELINE_TOKENS.include?(token)) && end_of_statement?(last_token, last_kind)
217
+ is_optional_do = (token == "do" && seen_for_at.include?(add_after - 1))
218
+
219
+ last_token, last_kind = token, kind unless kind == :space
220
+ next if IGNORE_TOKENS.include?(kind)
221
+
222
+ track_module_nesting(token, kind)
223
+
224
+ seen_for_at << add_after if OPTIONAL_DO_TOKENS.include?(token)
225
+
226
+ if kind == :delimiter
227
+ track_delimiter(token)
228
+ elsif OPEN_TOKENS.keys.include?(token) && !is_optional_do && !is_singleline_if
229
+ @stack << token
230
+ add_after += 1
231
+ elsif token == OPEN_TOKENS[@stack.last]
232
+ popped = @stack.pop
233
+ track_module_nesting_end(popped)
234
+ if add_after == 0
235
+ remove_before += 1
236
+ else
237
+ add_after -= 1
238
+ end
239
+ elsif MIDWAY_TOKENS.include?(token)
240
+ if add_after == 0
241
+ remove_before += 1
242
+ add_after += 1
243
+ end
244
+ end
245
+ end
246
+
247
+ return [remove_before, add_after]
248
+ end
249
+
250
+ # If the code just before an "if" or "while" token on a line looks like the end of a statement,
251
+ # then we want to treat that "if" as a singleline, not multiline statement.
252
+ def end_of_statement?(last_token, last_kind)
253
+ (last_token =~ /^[)\]}\/]$/ || STATEMENT_END_TOKENS.include?(last_kind))
254
+ end
255
+
256
+ # Are we currently in the middle of a string literal.
257
+ #
258
+ # This is used to determine whether to re-indent a given line, we mustn't re-indent
259
+ # within string literals because to do so would actually change the value of the
260
+ # String!
261
+ #
262
+ # @return Boolean
263
+ def in_string?
264
+ !open_delimiters.empty?
265
+ end
266
+
267
+ # Given a string of Ruby code, use CodeRay to export the tokens.
268
+ #
269
+ # @param [String] string The Ruby to lex
270
+ # @return [Array] An Array of pairs of [token_value, token_type]
271
+ def tokenize(string)
272
+ tokens = CodeRay.scan(string, :ruby)
273
+ tokens = tokens.tokens.each_slice(2) if tokens.respond_to?(:tokens) # Coderay 1.0.0
274
+ tokens.to_a
275
+ end
276
+
277
+ # Update the internal state about what kind of strings are open.
278
+ #
279
+ # Most of the complication here comes from the fact that HEREDOCs can be nested. For
280
+ # normal strings (which can't be nested) we assume that CodeRay correctly pairs
281
+ # open-and-close delimiters so we don't bother checking what they are.
282
+ #
283
+ # @param [String] token The token (of type :delimiter)
284
+ def track_delimiter(token)
285
+ case token
286
+ when /^<<-(["'`]?)(.*)\\1/
287
+ @heredoc_queue << token
288
+ @close_heredocs[token] = /^\s*$2/
289
+ when @close_heredocs[@heredoc_queue.first]
290
+ @heredoc_queue.shift
291
+ else
292
+ if @string_start
293
+ @string_start = nil
294
+ else
295
+ @string_start = token
296
+ end
297
+ end
298
+ end
299
+
300
+ # All the open delimiters, in the order that they first appeared.
301
+ #
302
+ # @return [String]
303
+ def open_delimiters
304
+ @heredoc_queue + [@string_start].compact
305
+ end
306
+
307
+ # Return a string which restores the CodeRay string status to the correct value by
308
+ # opening HEREDOCs and strings.
309
+ #
310
+ # @return String
311
+ def open_delimiters_line
312
+ "puts #{open_delimiters.join(", ")}"
313
+ end
314
+
315
+ # Update the internal state relating to module nesting.
316
+ #
317
+ # It's responsible for adding to the @module_nesting array, which looks
318
+ # something like:
319
+ #
320
+ # [ ["class", "Foo"], ["module", "Bar::Baz"], ["class <<", "self"] ]
321
+ #
322
+ # A nil value in the @module_nesting array happens in two places: either
323
+ # when @awaiting_class is true and we're still waiting for the string to
324
+ # fill that space, or when a parse was rejected.
325
+ #
326
+ # At the moment this function is quite restricted about what formats it will
327
+ # parse, for example we disallow expressions after the class keyword. This
328
+ # could maybe be improved in the future.
329
+ #
330
+ # @param [String] token a token from Coderay
331
+ # @param [Symbol] kind the kind of that token
332
+ def track_module_nesting(token, kind)
333
+ if kind == :keyword && (token == "class" || token == "module")
334
+ @module_nesting << [token, nil]
335
+ @awaiting_class = true
336
+ elsif @awaiting_class
337
+ if kind == :operator && token == "<<" && @module_nesting.last[0] == "class"
338
+ @module_nesting.last[0] = "class <<"
339
+ @awaiting_class = true
340
+ elsif kind == :class && token =~ /\A(self|[A-Z:][A-Za-z0-9_:]*)\z/
341
+ @module_nesting.last[1] = token if kind == :class
342
+ @awaiting_class = false
343
+ else
344
+ # leave @module_nesting[-1]
345
+ @awaiting_class = false
346
+ end
347
+ end
348
+ end
349
+
350
+ # Update the internal state relating to module nesting on 'end'.
351
+ #
352
+ # If the current 'end' pairs up with a class or a module then we should
353
+ # pop an array off of @module_nesting
354
+ #
355
+ # @param [String] token a token from Coderay
356
+ # @param [Symbol] kind the kind of that token
357
+ def track_module_nesting_end(token, kind=:keyword)
358
+ if kind == :keyword && (token == "class" || token == "module")
359
+ @module_nesting.pop
360
+ end
361
+ end
362
+
363
+ # Return a list of strings which can be used to re-construct the Module.nesting at
364
+ # the current point in the file.
365
+ #
366
+ # Returns nil if the syntax of the file was not recognizable.
367
+ #
368
+ # @return [Array<String>]
369
+ def module_nesting
370
+ @module_nesting.map do |(kind, token)|
371
+ raise UnparseableNestingError, @module_nesting.inspect if token.nil?
372
+
373
+ "#{kind} #{token}"
374
+ end
375
+ end
376
+
377
+ # Return a string which, when printed, will rewrite the previous line with
378
+ # the correct indentation. Mostly useful for fixing 'end'.
379
+ #
380
+ # @param [String] prompt The user's prompt
381
+ # @param [String] code The code the user just typed in.
382
+ # @param [Fixnum] overhang (0) The number of chars to erase afterwards (i.e.,
383
+ # the difference in length between the old line and the new one).
384
+ # @return [String]
385
+ def correct_indentation(prompt, code, overhang=0)
386
+ prompt = prompt.delete("\001\002")
387
+ line_to_measure = Pry::Helpers::Text.strip_color(prompt) << code
388
+ whitespace = ' ' * overhang
389
+
390
+ _, cols = Terminal.screen_size
391
+
392
+ cols = cols.to_i
393
+ lines = (cols != 0 ? (line_to_measure.length / cols + 1) : 1).to_i
394
+
395
+ if Pry::Helpers::BaseHelpers.windows_ansi?
396
+ move_up = "\e[#{lines}F"
397
+ move_down = "\e[#{lines}E"
398
+ else
399
+ move_up = "\e[#{lines}A\e[0G"
400
+ move_down = "\e[#{lines}B\e[0G"
401
+ end
402
+
403
+ "#{move_up}#{prompt}#{colorize_code(code)}#{whitespace}#{move_down}"
404
+ end
405
+ end
406
+ end