irb 1.17.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a457883af2f8f76b44bca4458e71fb4260244d033ab119f0cbc828b94bce01b5
4
- data.tar.gz: 22dca36785746338f9b0928fd0a16d5a36c4766f9360be937869ab7e89e01966
3
+ metadata.gz: f5228c22000696caa8318b56171e1238202f103e63c470db4d215ce2d0e46625
4
+ data.tar.gz: ac556bc9f4fe3abfef0310a1330d609d424c1196e4a75ba086bae789b0e973d1
5
5
  SHA512:
6
- metadata.gz: 284c0962361358e0aed3f0ded870a8f2bacafe1ab52d68df26f6e97e48d92396b75e78ab7a7019fc35d555a2c4016957fe4ac3017828f506d58828d9c83df16c
7
- data.tar.gz: 8417bb9569aae0a3397db838c2ca51d45be16b473ee2f986fb0c03168c8f8f5c2800d30f13bb92436c0ca6cc4ff22f70abcf33db12f647e2c157772069735e96
6
+ metadata.gz: fe37b6ecb5348d4cee255f42fd0be29d75b609b7edf45e0e8b6699dcb2865c4d888fbb282087acbfe2eaa9235890e60f7be80545ddb292b9adb4d4c29fd8b111
7
+ data.tar.gz: 063aab58e48c5ff5e6bb5ca306ad820469c96a691d1f90d227a045c6fb2d9a5b423c76e4099a213fee21f079c52839556e13b8a8529779df982f418756eb89ff
data/lib/irb/color.rb CHANGED
@@ -69,6 +69,7 @@ module IRB # :nodoc:
69
69
  KEYWORD_IN: [GREEN],
70
70
  KEYWORD_DEF: [GREEN],
71
71
  KEYWORD_DO: [GREEN],
72
+ KEYWORD_DO_BLOCK: [GREEN],
72
73
  KEYWORD_DO_LOOP: [GREEN],
73
74
  KEYWORD_FOR: [GREEN],
74
75
  KEYWORD_BEGIN: [GREEN],
@@ -102,16 +103,17 @@ module IRB # :nodoc:
102
103
  STRING_END: [RED, BOLD],
103
104
  __END__: [GREEN],
104
105
  # tokens from syntax tree traversal
105
- method_name: [BLUE, BOLD],
106
+ method_name: [CYAN, BOLD],
107
+ message_name: [CYAN],
106
108
  symbol: [YELLOW],
107
109
  # special colorization
108
110
  error: [RED, REVERSE],
109
- const_env: [CYAN, BOLD],
110
111
  }.transform_values do |styles|
111
112
  styles.map { |style| "\e[#{style}m" }.join
112
113
  end
113
114
  CLEAR_SEQ = "\e[#{CLEAR}m"
114
- private_constant :TOKEN_SEQS, :CLEAR_SEQ
115
+ OPERATORS = %i(!= !~ =~ == === <=> > >= < <= & | ^ >> << - + % / * ** -@ +@ ~ ! [] []=)
116
+ private_constant :TOKEN_SEQS, :CLEAR_SEQ, :OPERATORS
115
117
 
116
118
  class << self
117
119
  def colorable?
@@ -168,12 +170,13 @@ module IRB # :nodoc:
168
170
  # IRB::ColorPrinter skips colorizing syntax invalid fragments
169
171
  return Reline::Unicode.escape_for_print(code) if ignore_error && !result.success?
170
172
 
173
+ prism_node, prism_tokens = result.value
171
174
  errors = result.errors
175
+
172
176
  unless complete
173
- errors = errors.reject { |error| error.message =~ /\Aexpected a|unexpected end-of-input|unterminated/ }
177
+ errors = filter_incomplete_code_errors(errors, prism_tokens)
174
178
  end
175
179
 
176
- prism_node, prism_tokens = result.value
177
180
  visitor = ColorizeVisitor.new
178
181
  prism_node.accept(visitor)
179
182
 
@@ -202,9 +205,7 @@ module IRB # :nodoc:
202
205
  next if start_line - 1 < line_index || (start_line - 1 == line_index && start_column < col)
203
206
 
204
207
  flush.call(start_line - 1, start_column)
205
- if type == :CONSTANT && value == 'ENV'
206
- color = TOKEN_SEQS[:const_env]
207
- elsif type == :__END__
208
+ if type == :__END__
208
209
  color = TOKEN_SEQS[type]
209
210
  end_line = start_line
210
211
  value = '__END__'
@@ -251,6 +252,31 @@ module IRB # :nodoc:
251
252
  super
252
253
  end
253
254
 
255
+ def visit_alias_method_node(node)
256
+ dispatch_alias_method_name node.new_name
257
+ dispatch_alias_method_name node.old_name
258
+ super
259
+ end
260
+
261
+ def visit_call_node(node)
262
+ if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
263
+ # Operators should not be colored as method call
264
+ elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
265
+ /\A\p{Upper}/.match?(node.name)
266
+ # Constant-like methods should not be colored as method call
267
+ else
268
+ dispatch node.message_loc, :message_name
269
+ end
270
+ super
271
+ end
272
+
273
+ def visit_call_operator_write_node(node)
274
+ dispatch node.message_loc, :message_name
275
+ super
276
+ end
277
+ alias visit_call_and_write_node visit_call_operator_write_node
278
+ alias visit_call_or_write_node visit_call_operator_write_node
279
+
254
280
  def visit_interpolated_symbol_node(node)
255
281
  dispatch node.opening_loc, :symbol
256
282
  node.parts.each do |part|
@@ -278,10 +304,41 @@ module IRB # :nodoc:
278
304
  dispatch node.closing_loc, :symbol
279
305
  end
280
306
  end
307
+
308
+ private
309
+
310
+ def dispatch_alias_method_name(node)
311
+ if node.type == :symbol_node && node.opening_loc.nil?
312
+ dispatch node.value_loc, :method_name
313
+ end
314
+ end
281
315
  end
282
316
 
283
317
  private
284
318
 
319
+ FILTERED_ERROR_TYPES = [
320
+ :class_name, :module_name, # `class`, `class owner_module`
321
+ :write_target_unexpected, # `a, b`
322
+ :parameter_wild_loose_comma, # `def f(a,`
323
+ :argument_no_forwarding_star, # `[*`
324
+ :argument_no_forwarding_star_star, # `f(**`
325
+ :argument_no_forwarding_ampersand, # `f(&`
326
+ :def_endless, # `def f =`
327
+ :embdoc_term, # `=begin`
328
+ ]
329
+
330
+ # Filter out syntax errors that are likely to be caused by incomplete code, to avoid showing misleading error highlights to users.
331
+ def filter_incomplete_code_errors(errors, tokens)
332
+ last_non_comment_space_token, = tokens.reverse_each.find do |t,|
333
+ t.type != :COMMENT && t.type != :EOF && t.type != :IGNORED_NEWLINE && t.type != :NEWLINE
334
+ end
335
+ last_offset = last_non_comment_space_token ? last_non_comment_space_token.location.end_offset : 0
336
+ errors.reject do |error|
337
+ error.message.match?(/\Aexpected a|unexpected end-of-input|unterminated/) ||
338
+ (error.location.end_offset == last_offset && FILTERED_ERROR_TYPES.include?(error.type))
339
+ end
340
+ end
341
+
285
342
  def without_circular_ref(obj, seen:, &block)
286
343
  return false if seen.key?(obj)
287
344
  seen[obj] = true
@@ -37,11 +37,46 @@ module IRB
37
37
  puts e.message
38
38
  end
39
39
 
40
+ # Returns formatted lines for display in the doc dialog popup.
41
+ def doc_dialog_content(name, width)
42
+ lines = []
43
+ lines << Color.colorize(name, [:BOLD, :BLUE]) + Color.colorize(" (command)", [:CYAN])
44
+ lines << ""
45
+ lines.concat(wrap_lines(description, width))
46
+ if help_message
47
+ lines << ""
48
+ lines.concat(wrap_lines(help_message, width))
49
+ end
50
+ lines
51
+ end
52
+
40
53
  private
41
54
 
42
55
  def highlight(text)
43
56
  Color.colorize(text, [:BOLD, :BLUE])
44
57
  end
58
+
59
+ def wrap_lines(text, width)
60
+ text.lines.flat_map do |line|
61
+ line = line.chomp
62
+ next [''] if line.empty?
63
+ next [line] if line.length <= width
64
+
65
+ indent = line[/\A\s*/]
66
+ parts = line.strip.split(/(\s+)/)
67
+ result = []
68
+ current = indent.dup
69
+ parts.each do |part|
70
+ if current != indent && current.length + part.length > width
71
+ result << current.rstrip
72
+ current = indent.dup
73
+ end
74
+ current << part unless current == indent && part.match?(/\A\s+\z/)
75
+ end
76
+ result << current.rstrip unless current == indent
77
+ result
78
+ end
79
+ end
45
80
  end
46
81
 
47
82
  def initialize(irb_context)
@@ -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
@@ -55,11 +55,13 @@ module IRB
55
55
 
56
56
  o = Output.new(grep: grep)
57
57
 
58
- 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
59
61
 
60
- o.dump("constants", obj.constants) if obj.respond_to?(:constants)
62
+ o.dump("constants", obj.constants) if obj_is_class_or_module
61
63
  dump_methods(o, klass, obj)
62
- o.dump("instance variables", obj.instance_variables)
64
+ o.dump("instance variables", Kernel.instance_method(:instance_variables).bind(obj).call)
63
65
  o.dump("class variables", klass.class_variables)
64
66
  o.dump("locals", locals) if locals
65
67
  o.print_result
@@ -67,7 +69,7 @@ module IRB
67
69
  end
68
70
 
69
71
  def dump_methods(o, klass, obj)
70
- 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
71
73
  dumped_mods = Array.new
72
74
  ancestors = klass.ancestors
73
75
  ancestors = ancestors.reject { |c| c >= Object } if klass < Object
@@ -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/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
 
@@ -222,6 +222,7 @@ module IRB
222
222
  # Heredoc closing contains trailing newline. We need to exclude it
223
223
  close_location_start(node.closing_loc) if node.closing_loc && !node.closing.empty?
224
224
  elsif node.opening
225
+ return if node.opening == '?' && node.closing.nil? # Character literal has no closing
225
226
  open_location(node.location, type, node.opening)
226
227
  if node.closing && node.closing != ''
227
228
  # Closing of `"#{\n` is "\n". We need to treat it as not-closed.