irb 1.14.0 → 1.18.0

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +5 -0
  3. data/CONTRIBUTING.md +52 -0
  4. data/EXTEND_IRB.md +3 -0
  5. data/Gemfile +11 -1
  6. data/README.md +13 -293
  7. data/doc/COMMAND_LINE_OPTIONS.md +69 -0
  8. data/doc/COMPARED_WITH_PRY.md +22 -0
  9. data/doc/Configurations.md +274 -0
  10. data/doc/EXTEND_IRB.md +122 -0
  11. data/doc/Index.md +705 -0
  12. data/doc/irb/irb.rd.ja +1 -1
  13. data/lib/irb/color.rb +251 -162
  14. data/lib/irb/color_printer.rb +10 -9
  15. data/lib/irb/command/base.rb +46 -13
  16. data/lib/irb/command/copy.rb +83 -0
  17. data/lib/irb/command/debug.rb +8 -6
  18. data/lib/irb/command/help.rb +1 -1
  19. data/lib/irb/command/history.rb +1 -1
  20. data/lib/irb/command/internal_helpers.rb +7 -4
  21. data/lib/irb/command/ls.rb +29 -15
  22. data/lib/irb/completion.rb +81 -57
  23. data/lib/irb/context.rb +132 -53
  24. data/lib/irb/debug/ui.rb +2 -4
  25. data/lib/irb/debug.rb +9 -12
  26. data/lib/irb/default_commands.rb +19 -8
  27. data/lib/irb/easter-egg.rb +13 -7
  28. data/lib/irb/ext/multi-irb.rb +2 -0
  29. data/lib/irb/history.rb +84 -55
  30. data/lib/irb/init.rb +7 -2
  31. data/lib/irb/input-method.rb +167 -130
  32. data/lib/irb/inspector.rb +42 -36
  33. data/lib/irb/lc/help-message +2 -2
  34. data/lib/irb/lc/ja/help-message +4 -4
  35. data/lib/irb/nesting_parser.rb +364 -213
  36. data/lib/irb/pager.rb +137 -7
  37. data/lib/irb/ruby-lex.rb +230 -302
  38. data/lib/irb/ruby_logo.aa +79 -37
  39. data/lib/irb/source_finder.rb +15 -22
  40. data/lib/irb/startup_message.rb +83 -0
  41. data/lib/irb/statement.rb +21 -0
  42. data/lib/irb/version.rb +2 -2
  43. data/lib/irb/workspace.rb +22 -31
  44. data/lib/irb.rb +148 -986
  45. data/man/irb.1 +37 -2
  46. metadata +42 -12
  47. data/.document +0 -4
  48. data/Rakefile +0 -54
  49. data/bin/console +0 -6
  50. data/bin/setup +0 -6
  51. data/irb.gemspec +0 -46
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IRB
4
+ module Command
5
+ class Copy < Base
6
+ category "Misc"
7
+ description "Copy expression output to clipboard"
8
+
9
+ help_message(<<~HELP)
10
+ Usage: copy ([expression])
11
+
12
+ When given:
13
+ - an expression, copy the inspect result of the expression to the clipboard.
14
+ - no arguments, copy the last evaluated result (`_`) to the clipboard.
15
+
16
+ Examples:
17
+
18
+ copy Foo.new
19
+ copy User.all.to_a
20
+ copy
21
+ HELP
22
+
23
+ def execute(arg)
24
+ # Copy last value if no expression was supplied
25
+ arg = '_' if arg.to_s.strip.empty?
26
+
27
+ value = irb_context.workspace.binding.eval(arg)
28
+ output = irb_context.inspect_method.inspect_value(value, +'', colorize: false).chomp
29
+
30
+ if clipboard_available?
31
+ copy_to_clipboard(output)
32
+ else
33
+ warn "System clipboard not found"
34
+ end
35
+ rescue StandardError => e
36
+ warn "Error: #{e}"
37
+ end
38
+
39
+ private
40
+
41
+ def copy_to_clipboard(text)
42
+ IO.popen(clipboard_program, 'w') do |io|
43
+ io.write(text)
44
+ end
45
+
46
+ raise IOError.new("Copying to clipboard failed") unless $? == 0
47
+
48
+ puts "Copied to system clipboard"
49
+ rescue Errno::ENOENT => e
50
+ warn e.message
51
+ warn "Is IRB.conf[:COPY_COMMAND] set to a bad value?"
52
+ end
53
+
54
+ def clipboard_program
55
+ @clipboard_program ||= if IRB.conf[:COPY_COMMAND]
56
+ IRB.conf[:COPY_COMMAND]
57
+ elsif executable?("clip.exe")
58
+ "clip.exe"
59
+ elsif executable?("pbcopy")
60
+ "pbcopy"
61
+ elsif executable?("xclip")
62
+ "xclip -selection clipboard"
63
+ end
64
+ end
65
+
66
+ def executable?(command)
67
+ if windows?
68
+ system("where #{command} > NUL 2>&1")
69
+ else
70
+ system("which #{command} > /dev/null 2>&1")
71
+ end
72
+ end
73
+
74
+ def clipboard_available?
75
+ !!clipboard_program
76
+ end
77
+
78
+ def windows?
79
+ /mingw|mswin/.match?(RUBY_PLATFORM)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -58,13 +58,15 @@ module IRB
58
58
  end
59
59
 
60
60
  class DebugCommand < Debug
61
- def self.category
62
- "Debugging"
63
- end
61
+ class << self
62
+ def category
63
+ "Debugging"
64
+ end
64
65
 
65
- def self.description
66
- command_name = self.name.split("::").last.downcase
67
- "Start the debugger of debug.gem and run its `#{command_name}` command."
66
+ def description
67
+ command_name = self.name.split("::").last.downcase
68
+ "Start the debugger of debug.gem and run its `#{command_name}` command."
69
+ end
68
70
  end
69
71
  end
70
72
  end
@@ -39,7 +39,7 @@ module IRB
39
39
 
40
40
  help_cmds = commands_grouped_by_categories.delete("Help")
41
41
  no_category_cmds = commands_grouped_by_categories.delete("No category")
42
- aliases = irb_context.instance_variable_get(:@user_aliases).map do |alias_name, target|
42
+ aliases = irb_context.instance_variable_get(:@command_aliases).map do |alias_name, target|
43
43
  { display_name: alias_name, description: "Alias for `#{target}`" }
44
44
  end
45
45
 
@@ -14,7 +14,7 @@ module IRB
14
14
 
15
15
  def execute(arg)
16
16
 
17
- if (match = arg&.match(/(-g|-G)\s+(?<grep>.+)\s*\n\z/))
17
+ if (match = arg&.match(/(-g|-G)\s+(?<grep>.+)\s*\z/))
18
18
  grep = Regexp.new(match[:grep])
19
19
  end
20
20
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'prism'
4
+
3
5
  module IRB
4
6
  module Command
5
7
  # Internal use only, for default command's backward compatibility.
@@ -7,9 +9,10 @@ module IRB
7
9
  def unwrap_string_literal(str)
8
10
  return if str.empty?
9
11
 
10
- sexp = Ripper.sexp(str)
11
- if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
12
- @irb_context.workspace.binding.eval(str).to_s
12
+ result = Prism.parse(str)
13
+ body = result.value.statements.body
14
+ if result.success? && body.size == 1 && body.first.is_a?(Prism::StringNode)
15
+ body.first.unescaped
13
16
  else
14
17
  str
15
18
  end
@@ -19,7 +22,7 @@ module IRB
19
22
  # Use throw and catch to handle arg that includes `;`
20
23
  # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }]
21
24
  catch(:EXTRACT_RUBY_ARGS) do
22
- @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}"
25
+ @irb_context.workspace.binding.eval "::IRB::Command.extract_ruby_args #{arg}"
23
26
  end || [[], {}]
24
27
  end
25
28
  end
@@ -11,7 +11,7 @@ module IRB
11
11
 
12
12
  module Command
13
13
  class Ls < Base
14
- include RubyArgsExtractor
14
+ class EvaluationError < StandardError; end
15
15
 
16
16
  category "Context"
17
17
  description "Show methods, constants, and variables."
@@ -22,40 +22,54 @@ module IRB
22
22
  -g [query] Filter the output with a query.
23
23
  HELP_MESSAGE
24
24
 
25
+ def evaluate(code)
26
+ @irb_context.workspace.binding.eval(code)
27
+ rescue Exception => e
28
+ puts "#{e.class}: #{e.message}"
29
+ raise EvaluationError
30
+ end
31
+
25
32
  def execute(arg)
26
33
  if match = arg.match(/\A(?<target>.+\s|)(-g|-G)\s+(?<grep>.+)$/)
27
- if match[:target].empty?
28
- use_main = true
29
- else
30
- obj = @irb_context.workspace.binding.eval(match[:target])
31
- end
34
+ target = match[:target]
32
35
  grep = Regexp.new(match[:grep])
36
+ elsif match = arg.match(/\A((?<target>.+),|)\s*grep:(?<grep>.+)/)
37
+ # Legacy style `ls obj, grep: /regexp/`
38
+ # Evaluation order should be eval(target) then eval(grep)
39
+ target = match[:target] || ''
40
+ grep_regexp_code = match[:grep]
33
41
  else
34
- args, kwargs = ruby_args(arg)
35
- use_main = args.empty?
36
- obj = args.first
37
- grep = kwargs[:grep]
42
+ target = arg.strip
38
43
  end
39
44
 
40
- if use_main
45
+ if target.empty?
41
46
  obj = irb_context.workspace.main
42
47
  locals = irb_context.workspace.binding.local_variables
48
+ else
49
+ obj = evaluate(target)
50
+ end
51
+
52
+ if grep_regexp_code
53
+ grep = evaluate(grep_regexp_code)
43
54
  end
44
55
 
45
56
  o = Output.new(grep: grep)
46
57
 
47
- klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
58
+ klass = Kernel.instance_method(:class).bind(obj).call
59
+ obj_is_class_or_module = Module === obj
60
+ klass = obj_is_class_or_module ? obj : klass
48
61
 
49
- o.dump("constants", obj.constants) if obj.respond_to?(:constants)
62
+ o.dump("constants", obj.constants) if obj_is_class_or_module
50
63
  dump_methods(o, klass, obj)
51
- o.dump("instance variables", obj.instance_variables)
64
+ o.dump("instance variables", Kernel.instance_method(:instance_variables).bind(obj).call)
52
65
  o.dump("class variables", klass.class_variables)
53
66
  o.dump("locals", locals) if locals
54
67
  o.print_result
68
+ rescue EvaluationError
55
69
  end
56
70
 
57
71
  def dump_methods(o, klass, obj)
58
- singleton_class = begin obj.singleton_class; rescue TypeError; nil end
72
+ singleton_class = begin Kernel.instance_method(:singleton_class).bind(obj).call; rescue TypeError; nil end
59
73
  dumped_mods = Array.new
60
74
  ancestors = klass.ancestors
61
75
  ancestors = ancestors.reject { |c| c >= Object } if klass < Object
@@ -8,30 +8,33 @@
8
8
  require_relative 'ruby-lex'
9
9
 
10
10
  module IRB
11
+ class DocumentTarget # :nodoc:
12
+ attr_reader :name
13
+
14
+ def initialize(name)
15
+ @name = name
16
+ end
17
+ end
18
+
19
+ class CommandDocument < DocumentTarget # :nodoc:
20
+ end
21
+
22
+ # Represents a method/class documentation target. May hold multiple names
23
+ # when the receiver is ambiguous (e.g. `{}.any?` could be Hash#any? or Proc#any?).
24
+ # The dialog popup uses only the first name; the full-screen display renders all.
25
+ class MethodDocument < DocumentTarget # :nodoc:
26
+ attr_reader :names
27
+
28
+ def initialize(*names)
29
+ super(names.first)
30
+ @names = names
31
+ end
32
+ end
33
+
11
34
  class BaseCompletor # :nodoc:
12
35
 
13
36
  # Set of reserved words used by Ruby, you should not use these for
14
37
  # constants or variables
15
- ReservedWords = %w[
16
- __ENCODING__ __LINE__ __FILE__
17
- BEGIN END
18
- alias and
19
- begin break
20
- case class
21
- def defined? do
22
- else elsif end ensure
23
- false for
24
- if in
25
- module
26
- next nil not
27
- or
28
- redo rescue retry return
29
- self super
30
- then true
31
- undef unless until
32
- when while
33
- yield
34
- ]
35
38
 
36
39
  HELP_COMMAND_PREPOSING = /\Ahelp\s+/
37
40
 
@@ -60,13 +63,13 @@ module IRB
60
63
 
61
64
  def retrieve_gem_and_system_load_path
62
65
  candidates = (GEM_PATHS | $LOAD_PATH)
63
- candidates.map do |p|
66
+ candidates.filter_map do |p|
64
67
  if p.respond_to?(:to_path)
65
68
  p.to_path
66
69
  else
67
70
  String(p) rescue nil
68
71
  end
69
- end.compact.sort
72
+ end.sort
70
73
  end
71
74
 
72
75
  def retrieve_files_to_require_from_load_path
@@ -96,6 +99,12 @@ module IRB
96
99
  end
97
100
  end
98
101
 
102
+ def command_document_target(preposing, matched)
103
+ if preposing.empty? && IRB::Command.command_names.include?(matched)
104
+ CommandDocument.new(matched)
105
+ end
106
+ end
107
+
99
108
  def retrieve_files_to_require_relative_from_current_dir
100
109
  @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path|
101
110
  path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '')
@@ -127,36 +136,52 @@ module IRB
127
136
 
128
137
  return commands unless result
129
138
 
130
- commands | result.completion_candidates.map { target + _1 }
139
+ encoded_candidates = result.completion_candidates.filter_map do |i|
140
+ encoded = i.encode(Encoding.default_external)
141
+ target + encoded
142
+ rescue Encoding::UndefinedConversionError
143
+ # If the string cannot be converted, we just ignore it
144
+ nil
145
+ end
146
+ commands | encoded_candidates
131
147
  end
132
148
 
133
149
  def doc_namespace(preposing, matched, _postposing, bind:)
134
- result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path)
135
- result&.doc_namespace('')
150
+ command_document_target(preposing, matched) || begin
151
+ result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path)
152
+ result&.doc_namespace('')
153
+ end
136
154
  end
137
155
  end
138
156
 
139
157
  class RegexpCompletor < BaseCompletor # :nodoc:
158
+ KERNEL_METHODS = ::Kernel.instance_method(:methods)
159
+ KERNEL_PRIVATE_METHODS = ::Kernel.instance_method(:private_methods)
160
+ KERNEL_INSTANCE_VARIABLES = ::Kernel.instance_method(:instance_variables)
161
+ OBJECT_CLASS_INSTANCE_METHOD = ::Object.instance_method(:class)
162
+ MODULE_CONSTANTS_INSTANCE_METHOD = ::Module.instance_method(:constants)
163
+
140
164
  using Module.new {
141
165
  refine ::Binding do
142
166
  def eval_methods
143
- ::Kernel.instance_method(:methods).bind(eval("self")).call
167
+ KERNEL_METHODS.bind_call(receiver)
144
168
  end
145
169
 
146
170
  def eval_private_methods
147
- ::Kernel.instance_method(:private_methods).bind(eval("self")).call
171
+ KERNEL_PRIVATE_METHODS.bind_call(receiver)
148
172
  end
149
173
 
150
174
  def eval_instance_variables
151
- ::Kernel.instance_method(:instance_variables).bind(eval("self")).call
175
+ KERNEL_INSTANCE_VARIABLES.bind_call(receiver)
152
176
  end
153
177
 
154
178
  def eval_global_variables
155
- ::Kernel.instance_method(:global_variables).bind(eval("self")).call
179
+ ::Kernel.global_variables
156
180
  end
157
181
 
158
182
  def eval_class_constants
159
- ::Module.instance_method(:constants).bind(eval("self.class")).call
183
+ klass = OBJECT_CLASS_INSTANCE_METHOD.bind_call(receiver)
184
+ MODULE_CONSTANTS_INSTANCE_METHOD.bind_call(klass)
160
185
  end
161
186
  end
162
187
  }
@@ -172,28 +197,15 @@ module IRB
172
197
  else
173
198
  return nil # It's not String literal
174
199
  end
175
- tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, ''))
176
- tok = nil
177
- tokens.reverse_each do |t|
178
- unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event)
179
- tok = t
180
- break
181
- end
182
- end
183
- return unless tok&.event == :on_ident && tok.state == Ripper::EXPR_CMDARG
184
-
185
- case tok.tok
186
- when 'require'
187
- retrieve_files_to_require_from_load_path.select { |path|
188
- path.start_with?(actual_target)
189
- }.map { |path|
190
- quote + path
200
+
201
+ case preposing
202
+ when /(^|[^\w])require\(? *\z/
203
+ retrieve_files_to_require_from_load_path.filter_map { |path|
204
+ quote + path if path.start_with?(actual_target)
191
205
  }
192
- when 'require_relative'
193
- retrieve_files_to_require_relative_from_current_dir.select { |path|
194
- path.start_with?(actual_target)
195
- }.map { |path|
196
- quote + path
206
+ when /(^|[^\w])require_relative\(? *\z/
207
+ retrieve_files_to_require_relative_from_current_dir.filter_map { |path|
208
+ quote + path if path.start_with?(actual_target)
197
209
  }
198
210
  end
199
211
  end
@@ -211,12 +223,17 @@ module IRB
211
223
  # It doesn't make sense to propose commands with other preposing
212
224
  commands = [] unless preposing.empty?
213
225
 
214
- completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) }
226
+ completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.filter_map do |i|
227
+ i.encode(Encoding.default_external)
228
+ rescue Encoding::UndefinedConversionError
229
+ # If the string cannot be converted, we just ignore it
230
+ nil
231
+ end
215
232
  commands | completion_data
216
233
  end
217
234
 
218
- def doc_namespace(_preposing, matched, _postposing, bind:)
219
- retrieve_completion_data(matched, bind: bind, doc_namespace: true)
235
+ def doc_namespace(preposing, matched, _postposing, bind:)
236
+ command_document_target(preposing, matched) || retrieve_completion_data(matched, bind: bind, doc_namespace: true)
220
237
  end
221
238
 
222
239
  def retrieve_completion_data(input, bind:, doc_namespace:)
@@ -280,7 +297,7 @@ module IRB
280
297
  nil
281
298
  else
282
299
  sym = $1
283
- candidates = Symbol.all_symbols.collect do |s|
300
+ candidates = Symbol.all_symbols.filter_map do |s|
284
301
  s.inspect
285
302
  rescue EncodingError
286
303
  # ignore
@@ -452,12 +469,19 @@ module IRB
452
469
  eval("#{perfect_match_var}.class.name", bind) rescue nil
453
470
  else
454
471
  candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
455
- candidates |= ReservedWords
472
+ candidates |= RubyLex::RESERVED_WORDS.map(&:to_s)
456
473
  candidates.find{ |i| i == input }
457
474
  end
458
475
  else
459
476
  candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
460
- candidates |= ReservedWords
477
+ candidates |= RubyLex::RESERVED_WORDS.map(&:to_s)
478
+
479
+ target_encoding = Encoding.default_external
480
+ candidates = candidates.compact.filter_map do |candidate|
481
+ candidate.encoding == target_encoding ? candidate : candidate.encode(target_encoding)
482
+ rescue EncodingError
483
+ nil
484
+ end
461
485
  candidates.grep(/^#{Regexp.quote(input)}/).sort
462
486
  end
463
487
  end