irb 1.16.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.
@@ -8,6 +8,29 @@
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
@@ -76,6 +99,12 @@ module IRB
76
99
  end
77
100
  end
78
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
+
79
108
  def retrieve_files_to_require_relative_from_current_dir
80
109
  @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path|
81
110
  path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '')
@@ -118,8 +147,10 @@ module IRB
118
147
  end
119
148
 
120
149
  def doc_namespace(preposing, matched, _postposing, bind:)
121
- result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path)
122
- 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
123
154
  end
124
155
  end
125
156
 
@@ -166,22 +197,13 @@ module IRB
166
197
  else
167
198
  return nil # It's not String literal
168
199
  end
169
- tokens = RubyLex.ripper_lex_without_warning(preposing.rstrip)
170
- tok = nil
171
- tokens.reverse_each do |t|
172
- unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event)
173
- tok = t
174
- break
175
- end
176
- end
177
- return unless tok&.event == :on_ident && tok.state == Ripper::EXPR_CMDARG
178
200
 
179
- case tok.tok
180
- when 'require'
201
+ case preposing
202
+ when /(^|[^\w])require\(? *\z/
181
203
  retrieve_files_to_require_from_load_path.filter_map { |path|
182
204
  quote + path if path.start_with?(actual_target)
183
205
  }
184
- when 'require_relative'
206
+ when /(^|[^\w])require_relative\(? *\z/
185
207
  retrieve_files_to_require_relative_from_current_dir.filter_map { |path|
186
208
  quote + path if path.start_with?(actual_target)
187
209
  }
@@ -210,8 +232,8 @@ module IRB
210
232
  commands | completion_data
211
233
  end
212
234
 
213
- def doc_namespace(_preposing, matched, _postposing, bind:)
214
- 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)
215
237
  end
216
238
 
217
239
  def retrieve_completion_data(input, bind:, doc_namespace:)
data/lib/irb/context.rb CHANGED
@@ -649,7 +649,7 @@ module IRB
649
649
  parsed_input = parse_input(input, false)
650
650
  if parsed_input.is_a?(Statement::Command)
651
651
  name, sep, arg = input.split(/(\s+)/, 2)
652
- arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars)
652
+ arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) if arg
653
653
  "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}"
654
654
  else
655
655
  IRB::Color.colorize_code(input, complete: complete, local_variables: lvars)
data/lib/irb/debug.rb CHANGED
@@ -49,7 +49,7 @@ module IRB
49
49
  def DEBUGGER__.capture_frames(*args)
50
50
  frames = capture_frames_without_irb(*args)
51
51
  frames.reject! do |frame|
52
- frame.realpath&.start_with?(IRB_DIR) || frame.path.start_with?("<internal:")
52
+ frame.realpath&.start_with?(IRB_DIR) || frame.path&.start_with?("<internal:")
53
53
  end
54
54
  frames
55
55
  end
@@ -87,7 +87,7 @@ module IRB
87
87
  module SkipPathHelperForIRB
88
88
  def skip_internal_path?(path)
89
89
  # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved
90
- super || path.match?(IRB_DIR) || path.match?('<internal:prelude>')
90
+ super || path&.match?(IRB_DIR) || path&.match?('<internal:prelude>')
91
91
  end
92
92
  end
93
93
 
data/lib/irb/init.rb CHANGED
@@ -87,6 +87,7 @@ module IRB # :nodoc:
87
87
  @CONF[:IGNORE_SIGINT] = true
88
88
  @CONF[:IGNORE_EOF] = false
89
89
  @CONF[:USE_PAGER] = true
90
+ @CONF[:SHOW_BANNER] = true
90
91
  @CONF[:EXTRA_DOC_DIRS] = []
91
92
  @CONF[:ECHO] = nil
92
93
  @CONF[:ECHO_ON_ASSIGNMENT] = nil
@@ -345,6 +346,8 @@ module IRB # :nodoc:
345
346
  opt = $1 || argv.shift
346
347
  prompt_mode = opt.upcase.tr("-", "_").intern
347
348
  @CONF[:PROMPT_MODE] = prompt_mode
349
+ when "--nobanner"
350
+ @CONF[:SHOW_BANNER] = false
348
351
  when "--noprompt"
349
352
  @CONF[:PROMPT_MODE] = :NULL
350
353
  when "--script"
@@ -255,6 +255,16 @@ module IRB
255
255
 
256
256
  class RelineInputMethod < StdioInputMethod
257
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
258
268
  include HistorySavingAbility
259
269
  # Creates a new input method object using Reline
260
270
  def initialize(completor)
@@ -305,9 +315,17 @@ module IRB
305
315
  @auto_indent_proc = block
306
316
  end
307
317
 
308
- def retrieve_doc_namespace(matched)
318
+ def retrieve_document_target(matched)
309
319
  preposing, _target, postposing, bind = @completion_params
310
- @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
311
329
  end
312
330
 
313
331
  def rdoc_ri_driver
@@ -328,146 +346,158 @@ module IRB
328
346
  input_method = self # self is changed in the lambda below.
329
347
  ->() {
330
348
  dialog.trap_key = nil
331
- alt_d = [
332
- [27, 100], # Normal Alt+d when convert-meta isn't used.
333
- # When option/alt is not configured as a meta key in terminal emulator,
334
- # option/alt + d will send a unicode character depend on OS keyboard setting.
335
- [195, 164], # "ä" in somewhere (FIXME: environment information is unknown).
336
- [226, 136, 130] # "∂" Alt+d on Mac keyboard.
337
- ]
338
-
339
- if just_cursor_moving and completion_journey_data.nil?
349
+
350
+ if just_cursor_moving && completion_journey_data.nil?
340
351
  return nil
341
352
  end
342
353
  cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4)
343
- return nil if result.nil? or pointer.nil? or pointer < 0
354
+ return nil if result.nil? || pointer.nil? || pointer < 0
344
355
 
345
- name = input_method.retrieve_doc_namespace(result[pointer])
346
- # Use first one because document dialog does not support multiple namespaces.
347
- 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)
348
359
 
349
- 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
350
362
 
351
- driver = input_method.rdoc_ri_driver
363
+ dialog.trap_key = ALT_D_SEQUENCES
352
364
 
353
365
  if key.match?(dialog.name)
354
- if show_easter_egg
355
- IRB.__send__(:easter_egg)
356
- else
357
- # RDoc::RI::Driver#display_names uses pager command internally.
358
- # Some pager command like `more` doesn't use alternate screen
359
- # so we need to turn on and off alternate screen manually.
360
- begin
361
- print "\e[?1049h"
362
- driver.display_names([name])
363
- rescue RDoc::RI::Driver::NotFoundError
364
- ensure
365
- print "\e[?1049l"
366
- end
366
+ begin
367
+ print "\e[?1049h"
368
+ input_method.display_document(matched_text)
369
+ ensure
370
+ print "\e[?1049l"
367
371
  end
368
372
  end
369
373
 
370
- begin
371
- name = driver.expand_name(name)
372
- rescue RDoc::RI::Driver::NotFoundError
373
- return nil
374
- rescue
375
- return nil # unknown error
376
- end
377
- doc = nil
378
- used_for_class = false
379
- if not name =~ /#|\./
380
- found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name)
381
- if not found.empty?
382
- doc = driver.class_document(name, found, klasses, includes, extends)
383
- 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
384
382
  end
385
383
  end
386
- unless used_for_class
387
- doc = RDoc::Markup::Document.new
388
- begin
389
- driver.add_method(doc, name)
390
- rescue RDoc::RI::Driver::NotFoundError
391
- doc = nil
392
- rescue
393
- return nil # unknown error
394
- 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)
395
424
  end
396
- return nil if doc.nil?
397
- width = 40
398
-
399
- right_x = cursor_pos_to_render.x + autocomplete_dialog.width
400
- if right_x + width > screen_width
401
- right_width = screen_width - (right_x + 1)
402
- left_x = autocomplete_dialog.column - width
403
- left_x = 0 if left_x < 0
404
- left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width
405
- if right_width.positive? and left_width.positive?
406
- if right_width >= left_width
407
- width = right_width
408
- x = right_x
409
- else
410
- width = left_width
411
- x = left_x
412
- end
413
- 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
414
443
  width = right_width
415
444
  x = right_x
416
- elsif right_width <= 0 and left_width.positive?
445
+ else
417
446
  width = left_width
418
447
  x = left_x
419
- else # Both are negative width.
420
- return nil
421
448
  end
422
- else
449
+ elsif right_width.positive? && left_width <= 0
450
+ width = right_width
423
451
  x = right_x
424
- end
425
- formatter = RDoc::Markup::ToAnsi.new
426
- formatter.width = width
427
- dialog.trap_key = alt_d
428
- mod_key = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt"
429
- if show_easter_egg
430
- type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode : :ascii
431
- contents = IRB.send(:easter_egg_logo, type).split("\n")
432
- message = "Press #{mod_key}+d to see more"
433
- contents[0][0, message.size] = message
452
+ elsif right_width <= 0 && left_width.positive?
453
+ width = left_width
454
+ x = left_x
434
455
  else
435
- message = "Press #{mod_key}+d to read the full document"
436
- contents = [message] + doc.accept(formatter).split("\n")
456
+ return nil
437
457
  end
438
- contents = contents.take(preferred_dialog_height)
439
-
440
- y = cursor_pos_to_render.y
441
- Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49')
442
- }
458
+ else
459
+ x = right_x
460
+ end
461
+ [x, width]
443
462
  end
444
463
 
445
464
  def display_document(matched)
446
- driver = rdoc_ri_driver
447
- return unless driver
448
-
449
- if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
450
- IRB.__send__(:easter_egg)
451
- return
452
- 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
453
480
 
454
- namespace = retrieve_doc_namespace(matched)
455
- 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
456
485
 
457
- if namespace.is_a?(Array)
458
- out = RDoc::Markup::Document.new
459
- 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
460
496
  begin
461
- driver.add_method(out, m)
497
+ driver.display_names([target.name])
462
498
  rescue RDoc::RI::Driver::NotFoundError
463
499
  end
464
500
  end
465
- driver.display(out)
466
- else
467
- begin
468
- driver.display_names([namespace])
469
- rescue RDoc::RI::Driver::NotFoundError
470
- end
471
501
  end
472
502
  end
473
503