Class: RubyLsp::Listeners::Completion

Inherits:
RubyLsp::Listener show all
Extended by:
T::Generic, T::Sig
Defined in:
lib/ruby_lsp/listeners/completion.rb

Constant Summary collapse

ResponseType =
type_member { { fixed: T::Array[Interface::CompletionItem] } }

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from RubyLsp::Listener

#response

Methods included from Requests::Support::Common

#create_code_lens, #markdown_from_index_entries, #not_in_dependencies?, #range_from_location, #range_from_node, #self_receiver?, #visible?

Constructor Details

#initialize(index, nesting, typechecker_enabled, dispatcher) ⇒ Completion

Returns a new instance of Completion.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/ruby_lsp/listeners/completion.rb', line 23

def initialize(index, nesting, typechecker_enabled, dispatcher)
  super(dispatcher)
  @_response = T.let([], ResponseType)
  @index = index
  @nesting = nesting
  @typechecker_enabled = typechecker_enabled

  dispatcher.register(
    self,
    :on_string_node_enter,
    :on_constant_path_node_enter,
    :on_constant_read_node_enter,
    :on_call_node_enter,
  )
end

Instance Attribute Details

#_responseObject (readonly)

Returns the value of attribute _response.



13
14
15
# File 'lib/ruby_lsp/listeners/completion.rb', line 13

def _response
  @_response
end

Instance Method Details

#on_call_node_enter(node) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/ruby_lsp/listeners/completion.rb', line 110

def on_call_node_enter(node)
  return if @typechecker_enabled
  return unless self_receiver?(node)

  name = node.message
  return unless name

  receiver_entries = @index[@nesting.join("::")]
  return unless receiver_entries

  receiver = T.must(receiver_entries.first)

  @index.prefix_search(name).each do |entries|
    entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
    next unless entry

    @_response << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
  end
end

#on_constant_path_node_enter(node) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/ruby_lsp/listeners/completion.rb', line 67

def on_constant_path_node_enter(node)
  return if DependencyDetector.instance.typechecker

  name = node.slice

  top_level_reference = if name.start_with?("::")
    name = name.delete_prefix("::")
    true
  else
    false
  end

  # If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
  # order to find which possible constants match the desired search
  *namespace, incomplete_name = name.split("::")
  aliased_namespace = T.must(namespace).join("::")
  namespace_entries = @index.resolve(aliased_namespace, @nesting)
  return unless namespace_entries

  real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)

  candidates = @index.prefix_search("#{real_namespace}::#{incomplete_name}", top_level_reference ? [] : @nesting)
  candidates.each do |entries|
    # The only time we may have a private constant reference from outside of the namespace is if we're dealing
    # with ConstantPath and the entry name doesn't start with the current nesting
    first_entry = T.must(entries.first)
    next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")

    constant_name = T.must(first_entry.name.split("::").last)

    full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"

    @_response << build_entry_completion(
      full_name,
      name,
      node,
      entries,
      top_level_reference || top_level?(T.must(entries.first).name),
    )
  end
end

#on_constant_read_node_enter(node) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/ruby_lsp/listeners/completion.rb', line 48

def on_constant_read_node_enter(node)
  return if DependencyDetector.instance.typechecker

  name = node.slice
  candidates = @index.prefix_search(name, @nesting)
  candidates.each do |entries|
    complete_name = T.must(entries.first).name
    @_response << build_entry_completion(
      complete_name,
      name,
      node,
      entries,
      top_level?(complete_name),
    )
  end
end

#on_string_node_enter(node) ⇒ Object



40
41
42
43
44
# File 'lib/ruby_lsp/listeners/completion.rb', line 40

def on_string_node_enter(node)
  @index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
    @_response << build_completion(T.must(path), node)
  end
end