irb 1.14.3 → 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 (48) 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 -310
  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 +35 -0
  16. data/lib/irb/command/copy.rb +83 -0
  17. data/lib/irb/command/history.rb +1 -1
  18. data/lib/irb/command/internal_helpers.rb +6 -3
  19. data/lib/irb/command/ls.rb +6 -4
  20. data/lib/irb/completion.rb +69 -52
  21. data/lib/irb/context.rb +100 -64
  22. data/lib/irb/debug.rb +3 -3
  23. data/lib/irb/default_commands.rb +4 -1
  24. data/lib/irb/easter-egg.rb +3 -1
  25. data/lib/irb/ext/multi-irb.rb +2 -0
  26. data/lib/irb/history.rb +4 -0
  27. data/lib/irb/init.rb +6 -1
  28. data/lib/irb/input-method.rb +158 -122
  29. data/lib/irb/inspector.rb +12 -7
  30. data/lib/irb/lc/help-message +2 -2
  31. data/lib/irb/lc/ja/help-message +1 -1
  32. data/lib/irb/nesting_parser.rb +362 -213
  33. data/lib/irb/pager.rb +127 -6
  34. data/lib/irb/ruby-lex.rb +225 -299
  35. data/lib/irb/ruby_logo.aa +4 -0
  36. data/lib/irb/source_finder.rb +12 -18
  37. data/lib/irb/startup_message.rb +83 -0
  38. data/lib/irb/statement.rb +21 -0
  39. data/lib/irb/version.rb +2 -2
  40. data/lib/irb/workspace.rb +9 -0
  41. data/lib/irb.rb +112 -927
  42. data/man/irb.1 +2 -0
  43. metadata +42 -12
  44. data/.document +0 -8
  45. data/Rakefile +0 -52
  46. data/bin/console +0 -6
  47. data/bin/setup +0 -6
  48. data/irb.gemspec +0 -46
@@ -26,10 +26,11 @@ module IRB
26
26
 
27
27
  def winsize
28
28
  if instance_variable_defined?(:@stdout) && @stdout.tty?
29
- @stdout.winsize
30
- else
31
- [24, 80]
29
+ winsize = @stdout.winsize
30
+ # If width or height is 0, something is wrong.
31
+ return winsize unless winsize.include? 0
32
32
  end
33
+ [24, 80]
33
34
  end
34
35
 
35
36
  # Whether this input method is still readable when there is no more data to
@@ -175,10 +176,15 @@ module IRB
175
176
  class ReadlineInputMethod < StdioInputMethod
176
177
  class << self
177
178
  def initialize_readline
178
- require "readline"
179
- rescue LoadError
180
- else
181
- include ::Readline
179
+ return if defined?(self::Readline)
180
+
181
+ begin
182
+ require 'readline'
183
+ const_set(:Readline, ::Readline)
184
+ rescue LoadError
185
+ const_set(:Readline, ::Reline)
186
+ end
187
+ const_set(:HISTORY, self::Readline::HISTORY)
182
188
  end
183
189
  end
184
190
 
@@ -216,8 +222,8 @@ module IRB
216
222
  def gets
217
223
  Readline.input = @stdin
218
224
  Readline.output = @stdout
219
- if l = readline(@prompt, false)
220
- HISTORY.push(l) if !l.empty?
225
+ if l = Readline.readline(@prompt, false)
226
+ Readline::HISTORY.push(l) if !l.empty? && l != Readline::HISTORY.to_a.last
221
227
  @line[@line_no += 1] = l + "\n"
222
228
  else
223
229
  @eof = true
@@ -239,7 +245,7 @@ module IRB
239
245
 
240
246
  # For debug message
241
247
  def inspect
242
- readline_impl = (defined?(Reline) && Readline == Reline) ? 'Reline' : 'ext/readline'
248
+ readline_impl = Readline == ::Reline ? 'Reline' : 'ext/readline'
243
249
  str = "ReadlineInputMethod with #{readline_impl} #{Readline::VERSION}"
244
250
  inputrc_path = File.expand_path(ENV['INPUTRC'] || '~/.inputrc')
245
251
  str += " and #{inputrc_path}" if File.exist?(inputrc_path)
@@ -249,6 +255,16 @@ module IRB
249
255
 
250
256
  class RelineInputMethod < StdioInputMethod
251
257
  HISTORY = Reline::HISTORY
258
+ ALT_KEY_NAME = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt"
259
+ PRESS_ALT_D_TO_READ_FULL_DOC = "Press #{ALT_KEY_NAME}+d to read the full document".freeze
260
+ PRESS_ALT_D_TO_SEE_MORE = "Press #{ALT_KEY_NAME}+d to see more".freeze
261
+ ALT_D_SEQUENCES = [
262
+ [27, 100], # Normal Alt+d when convert-meta isn't used.
263
+ # When option/alt is not configured as a meta key in terminal emulator,
264
+ # option/alt + d will send a unicode character depend on OS keyboard setting.
265
+ [195, 164], # "ä" in somewhere (FIXME: environment information is unknown).
266
+ [226, 136, 130] # "∂" Alt+d on Mac keyboard.
267
+ ].freeze
252
268
  include HistorySavingAbility
253
269
  # Creates a new input method object using Reline
254
270
  def initialize(completor)
@@ -299,9 +315,17 @@ module IRB
299
315
  @auto_indent_proc = block
300
316
  end
301
317
 
302
- def retrieve_doc_namespace(matched)
318
+ def retrieve_document_target(matched)
303
319
  preposing, _target, postposing, bind = @completion_params
304
- @completor.doc_namespace(preposing, matched, postposing, bind: bind)
320
+ result = @completor.doc_namespace(preposing, matched, postposing, bind: bind)
321
+ case result
322
+ when DocumentTarget, nil
323
+ result
324
+ when Array
325
+ MethodDocument.new(*result)
326
+ when String
327
+ MethodDocument.new(result)
328
+ end
305
329
  end
306
330
 
307
331
  def rdoc_ri_driver
@@ -322,146 +346,158 @@ module IRB
322
346
  input_method = self # self is changed in the lambda below.
323
347
  ->() {
324
348
  dialog.trap_key = nil
325
- alt_d = [
326
- [27, 100], # Normal Alt+d when convert-meta isn't used.
327
- # When option/alt is not configured as a meta key in terminal emulator,
328
- # option/alt + d will send a unicode character depend on OS keyboard setting.
329
- [195, 164], # "ä" in somewhere (FIXME: environment information is unknown).
330
- [226, 136, 130] # "∂" Alt+d on Mac keyboard.
331
- ]
332
-
333
- if just_cursor_moving and completion_journey_data.nil?
349
+
350
+ if just_cursor_moving && completion_journey_data.nil?
334
351
  return nil
335
352
  end
336
353
  cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4)
337
- return nil if result.nil? or pointer.nil? or pointer < 0
354
+ return nil if result.nil? || pointer.nil? || pointer < 0
338
355
 
339
- name = input_method.retrieve_doc_namespace(result[pointer])
340
- # Use first one because document dialog does not support multiple namespaces.
341
- name = name.first if name.is_a?(Array)
356
+ matched_text = result[pointer]
357
+ show_easter_egg = matched_text&.match?(/\ARubyVM/) && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
358
+ target = show_easter_egg ? nil : input_method.retrieve_document_target(matched_text)
342
359
 
343
- show_easter_egg = name&.match?(/\ARubyVM/) && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
360
+ x, width = input_method.dialog_doc_position(cursor_pos_to_render, autocomplete_dialog, screen_width)
361
+ return nil unless x
344
362
 
345
- driver = input_method.rdoc_ri_driver
363
+ dialog.trap_key = ALT_D_SEQUENCES
346
364
 
347
365
  if key.match?(dialog.name)
348
- if show_easter_egg
349
- IRB.__send__(:easter_egg)
350
- else
351
- # RDoc::RI::Driver#display_names uses pager command internally.
352
- # Some pager command like `more` doesn't use alternate screen
353
- # so we need to turn on and off alternate screen manually.
354
- begin
355
- print "\e[?1049h"
356
- driver.display_names([name])
357
- rescue RDoc::RI::Driver::NotFoundError
358
- ensure
359
- print "\e[?1049l"
360
- end
366
+ begin
367
+ print "\e[?1049h"
368
+ input_method.display_document(matched_text)
369
+ ensure
370
+ print "\e[?1049l"
361
371
  end
362
372
  end
363
373
 
364
- begin
365
- name = driver.expand_name(name)
366
- rescue RDoc::RI::Driver::NotFoundError
367
- return nil
368
- rescue
369
- return nil # unknown error
370
- end
371
- doc = nil
372
- used_for_class = false
373
- if not name =~ /#|\./
374
- found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name)
375
- if not found.empty?
376
- doc = driver.class_document(name, found, klasses, includes, extends)
377
- used_for_class = true
374
+ contents = case target
375
+ when CommandDocument
376
+ input_method.command_doc_dialog_contents(target.name, width)
377
+ when MethodDocument
378
+ input_method.rdoc_dialog_contents(target.name, width)
379
+ else
380
+ if show_easter_egg
381
+ input_method.easter_egg_dialog_contents
378
382
  end
379
383
  end
380
- unless used_for_class
381
- doc = RDoc::Markup::Document.new
382
- begin
383
- driver.add_method(doc, name)
384
- rescue RDoc::RI::Driver::NotFoundError
385
- doc = nil
386
- rescue
387
- return nil # unknown error
388
- end
384
+ return nil unless contents
385
+
386
+ contents = contents.take(preferred_dialog_height)
387
+ y = cursor_pos_to_render.y
388
+ Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49')
389
+ }
390
+ end
391
+
392
+ def command_doc_dialog_contents(command_name, width)
393
+ command_class = IRB::Command.load_command(command_name)
394
+ return unless command_class
395
+
396
+ [PRESS_ALT_D_TO_READ_FULL_DOC, ""] + command_class.doc_dialog_content(command_name, width)
397
+ end
398
+
399
+ def easter_egg_dialog_contents
400
+ type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode : :ascii
401
+ lines = IRB.send(:easter_egg_logo, type).split("\n")
402
+ lines[0][0, PRESS_ALT_D_TO_SEE_MORE.size] = PRESS_ALT_D_TO_SEE_MORE
403
+ lines
404
+ end
405
+
406
+ def rdoc_dialog_contents(name, width)
407
+ driver = rdoc_ri_driver
408
+ return unless driver
409
+
410
+ name = driver.expand_name(name)
411
+
412
+ doc = if name =~ /#|\./
413
+ d = RDoc::Markup::Document.new
414
+ driver.add_method(d, name)
415
+ d
416
+ else
417
+ found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name)
418
+ if found.empty?
419
+ d = RDoc::Markup::Document.new
420
+ driver.add_method(d, name)
421
+ d
422
+ else
423
+ driver.class_document(name, found, klasses, includes, extends)
389
424
  end
390
- return nil if doc.nil?
391
- width = 40
392
-
393
- right_x = cursor_pos_to_render.x + autocomplete_dialog.width
394
- if right_x + width > screen_width
395
- right_width = screen_width - (right_x + 1)
396
- left_x = autocomplete_dialog.column - width
397
- left_x = 0 if left_x < 0
398
- left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width
399
- if right_width.positive? and left_width.positive?
400
- if right_width >= left_width
401
- width = right_width
402
- x = right_x
403
- else
404
- width = left_width
405
- x = left_x
406
- end
407
- elsif right_width.positive? and left_width <= 0
425
+ end
426
+
427
+ formatter = RDoc::Markup::ToAnsi.new
428
+ formatter.width = width
429
+ [PRESS_ALT_D_TO_READ_FULL_DOC] + doc.accept(formatter).split("\n")
430
+ rescue RDoc::RI::Driver::NotFoundError
431
+ end
432
+
433
+ def dialog_doc_position(cursor_pos_to_render, autocomplete_dialog, screen_width)
434
+ width = 40
435
+ right_x = cursor_pos_to_render.x + autocomplete_dialog.width
436
+ if right_x + width > screen_width
437
+ right_width = screen_width - (right_x + 1)
438
+ left_x = autocomplete_dialog.column - width
439
+ left_x = 0 if left_x < 0
440
+ left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width
441
+ if right_width.positive? && left_width.positive?
442
+ if right_width >= left_width
408
443
  width = right_width
409
444
  x = right_x
410
- elsif right_width <= 0 and left_width.positive?
445
+ else
411
446
  width = left_width
412
447
  x = left_x
413
- else # Both are negative width.
414
- return nil
415
448
  end
416
- else
449
+ elsif right_width.positive? && left_width <= 0
450
+ width = right_width
417
451
  x = right_x
418
- end
419
- formatter = RDoc::Markup::ToAnsi.new
420
- formatter.width = width
421
- dialog.trap_key = alt_d
422
- mod_key = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt"
423
- if show_easter_egg
424
- type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode : :ascii
425
- contents = IRB.send(:easter_egg_logo, type).split("\n")
426
- message = "Press #{mod_key}+d to see more"
427
- contents[0][0, message.size] = message
452
+ elsif right_width <= 0 && left_width.positive?
453
+ width = left_width
454
+ x = left_x
428
455
  else
429
- message = "Press #{mod_key}+d to read the full document"
430
- contents = [message] + doc.accept(formatter).split("\n")
456
+ return nil
431
457
  end
432
- contents = contents.take(preferred_dialog_height)
433
-
434
- y = cursor_pos_to_render.y
435
- Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49')
436
- }
458
+ else
459
+ x = right_x
460
+ end
461
+ [x, width]
437
462
  end
438
463
 
439
464
  def display_document(matched)
440
- driver = rdoc_ri_driver
441
- return unless driver
442
-
443
- if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
444
- IRB.__send__(:easter_egg)
445
- return
446
- end
465
+ target = retrieve_document_target(matched)
466
+ return unless target
467
+
468
+ case target
469
+ when CommandDocument
470
+ command_class = IRB::Command.load_command(target.name)
471
+ if command_class
472
+ content = command_class.help_message || command_class.description
473
+ Pager.page(retain_content: true) do |io|
474
+ io.puts content
475
+ end
476
+ end
477
+ when MethodDocument
478
+ driver = rdoc_ri_driver
479
+ return unless driver
447
480
 
448
- namespace = retrieve_doc_namespace(matched)
449
- return unless namespace
481
+ if matched =~ /\A(?:::)?RubyVM/ && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
482
+ IRB.__send__(:easter_egg)
483
+ return
484
+ end
450
485
 
451
- if namespace.is_a?(Array)
452
- out = RDoc::Markup::Document.new
453
- namespace.each do |m|
486
+ if target.names.length > 1
487
+ out = RDoc::Markup::Document.new
488
+ target.names.each do |m|
489
+ begin
490
+ driver.add_method(out, m)
491
+ rescue RDoc::RI::Driver::NotFoundError
492
+ end
493
+ end
494
+ driver.display(out)
495
+ else
454
496
  begin
455
- driver.add_method(out, m)
497
+ driver.display_names([target.name])
456
498
  rescue RDoc::RI::Driver::NotFoundError
457
499
  end
458
500
  end
459
- driver.display(out)
460
- else
461
- begin
462
- driver.display_names([namespace])
463
- rescue RDoc::RI::Driver::NotFoundError
464
- end
465
501
  end
466
502
  end
467
503
 
@@ -474,7 +510,7 @@ module IRB
474
510
  Reline.prompt_proc = @prompt_proc
475
511
  Reline.auto_indent_proc = @auto_indent_proc if @auto_indent_proc
476
512
  if l = Reline.readmultiline(@prompt, false, &@check_termination_proc)
477
- Reline::HISTORY.push(l) if !l.empty?
513
+ Reline::HISTORY.push(l) if !l.empty? && l != Reline::HISTORY.to_a.last
478
514
  @line[@line_no += 1] = l + "\n"
479
515
  else
480
516
  @eof = true
data/lib/irb/inspector.rb CHANGED
@@ -46,7 +46,7 @@ module IRB # :nodoc:
46
46
  # Determines the inspector to use where +inspector+ is one of the keys passed
47
47
  # during inspector definition.
48
48
  def keys_with_inspector(inspector)
49
- INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k}
49
+ INSPECTORS.filter_map {|k, v| k if v == inspector}
50
50
  end
51
51
 
52
52
  # Example
@@ -92,9 +92,14 @@ module IRB # :nodoc:
92
92
  @init.call if @init
93
93
  end
94
94
 
95
+ def support_stream_output?
96
+ second_parameter_type = @inspect.parameters[1]&.first
97
+ second_parameter_type == :req || second_parameter_type == :opt
98
+ end
99
+
95
100
  # Proc to call when the input is evaluated and output in irb.
96
- def inspect_value(v)
97
- @inspect.call(v)
101
+ def inspect_value(v, output, colorize: true)
102
+ support_stream_output? ? @inspect.call(v, output, colorize: colorize) : output << @inspect.call(v, colorize: colorize)
98
103
  rescue => e
99
104
  puts "An error occurred when inspecting the object: #{e.inspect}"
100
105
 
@@ -110,11 +115,11 @@ module IRB # :nodoc:
110
115
  end
111
116
 
112
117
  Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s}
113
- Inspector.def_inspector([:p, :inspect]){|v|
114
- Color.colorize_code(v.inspect, colorable: Color.colorable? && Color.inspect_colorable?(v))
118
+ Inspector.def_inspector([:p, :inspect]){|v, colorize: true|
119
+ Color.colorize_code(v.inspect, colorable: colorize && Color.colorable? && Color.inspect_colorable?(v))
115
120
  }
116
- Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v|
117
- IRB::ColorPrinter.pp(v, +'').chomp
121
+ Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, output, colorize: true|
122
+ IRB::ColorPrinter.pp(v, output, colorize: colorize)
118
123
  }
119
124
  Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v|
120
125
  begin
@@ -6,10 +6,10 @@ Usage: irb.rb [options] [programfile] [arguments]
6
6
  -U Set external and internal encodings to UTF-8.
7
7
  -E ex[:in] Set default external (ex) and internal (in) encodings
8
8
  (same as 'ruby -E').
9
- -w Suppress warnings (same as 'ruby -w').
9
+ -w Enable warnings (same as 'ruby -w' or 'ruby -W1').
10
10
  -W[level=2] Set warning level: 0=silence, 1=medium, 2=verbose
11
11
  (same as 'ruby -W').
12
- --context-mode n Set n[0-4] to method to create Binding Object,
12
+ --context-mode n Set n[0-5] to method to create Binding Object,
13
13
  when new workspace was created.
14
14
  --extra-doc-dir Add an extra doc dir for the doc dialog.
15
15
  --echo Show result (default).
@@ -8,7 +8,7 @@ Usage: irb.rb [options] [programfile] [arguments]
8
8
  -w ruby -w と同じ.
9
9
  -W[level=2] ruby -W と同じ.
10
10
  --context-mode n 新しいワークスペースを作成した時に関連する Binding
11
- オブジェクトの作成方法を 0 から 4 のいずれかに設定する.
11
+ オブジェクトの作成方法を 0 から 5 のいずれかに設定する.
12
12
  --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む.
13
13
  --echo 実行結果を表示する(デフォルト).
14
14
  --noecho 実行結果を表示しない.