@@ -167,34 +167,61 @@ def possible_definitions(uri, position)
167167
168168 context = context_at_location ( uri , position )
169169
170+ # Get current scopes for context
171+ current_scopes = scopes_at ( uri , position )
172+
170173 # If context has more than one element it could be a method call or namespace reference
171174 if context . length > 1
172- # Check if this is a namespace reference (Foo::Bar) vs method call (Foo.bar)
173- if namespace_reference? ( uri , position , context )
174- # Join the context with :: to form the full class/module name
175- full_name = context . join ( '::' )
176- return project_definitions_for ( full_name )
175+ # Find the rightmost class/module reference in the context (excluding the last element which is the name)
176+ # Examples:
177+ # - foo.Bar::Baz.something -> find Baz (index 2), build scope from Bar::Baz
178+ # - Bar.foo.Baz.something -> find Baz (index 2), use Baz as scope
179+ class_module_indices = [ ]
180+ ( 0 ...( context . length - 1 ) ) . each do |i |
181+ class_module_indices << i if likely_class_name? ( context [ i ] )
177182 end
178183
179- receiver = context . first
180- # Determine if it's a class method call (Foo.method) or instance method call (foo.method)
181- class_method_filter = name != 'initialize'
182- return project_definitions_for ( name , class_method_filter ) if likely_class_name? ( receiver )
184+ if class_module_indices . any?
185+ # Use the rightmost class/module and build the path from there to just before the name
186+ rightmost_class_index = class_module_indices . last
187+ scope_path_parts = context [ rightmost_class_index ..-2 ]
188+
189+ if scope_path_parts . empty?
190+ # This shouldn't happen, but handle it gracefully
191+ return project_definitions_for ( name , current_scopes )
192+ elsif scope_path_parts . length == 1
193+ # Single class/module like Bar.something or Foo::Bar
194+ parent_scope = find_scope_by_path ( scope_path_parts . first )
195+ else
196+ # Multiple parts like Bar::Baz.something or after finding Bar in foo.Bar::Baz.something
197+ scope_path = scope_path_parts . join ( '::' )
198+ parent_scope = find_scope_by_path ( scope_path )
199+ end
200+
201+ # Determine if it's a class/module lookup or a method call
202+ # If the name also looks like a class/module name, it's a namespace lookup (Foo::Bar)
203+ # Otherwise it's a method call (Foo.method or Foo::Bar.method)
204+ return project_definitions_for ( name , parent_scope ? [ parent_scope ] : [ ] ) if likely_class_name? ( name ) || constant_name? ( name )
183205
184- # Class method call or MyClass.new (which finds initialize as instance method)
185- # initialize is weird because it's defined as an instance method but called on the class via new.
206+ # Namespace lookup like Foo::Bar or Foo::BAR - no method type filtering
186207
187- # Instance method call (e.g., foo.bar, @foo.bar, FOO.bar)
188- return project_definitions_for ( name , false )
208+ # Method call - determine if it's a class method or instance method
209+ class_method_filter = name != 'initialize'
210+ return project_definitions_for ( name , parent_scope ? [ parent_scope ] : [ ] , class_method_filter )
189211
212+ end
213+
214+ # No class/module found in chain, treat as instance method call on unknown type
215+ # Search project-wide for all instance methods with this name
216+ return project_definitions_for ( name , [ ] , false )
190217 end
191218
192219 # No receiver - search in scope chain first, then project-wide
193- scope = scopes_at ( uri , position ) . first
220+ scope = current_scopes . first
194221 results = scope_definitions_for ( name , scope , uri )
195222 return results unless results . empty?
196223
197- project_definitions_for ( name )
224+ project_definitions_for ( name , current_scopes )
198225 end
199226
200227 # Return variables found in the current scope. After all, those are the important ones.
@@ -212,26 +239,65 @@ def scope_definitions_for(name, scope, uri)
212239 return_array . uniq
213240 end
214241
215- def project_definitions_for ( name , class_method_filter = nil )
216- # Check if name contains namespace separator (e.g., "Foo::Bar")
217- # If so, search by path instead of name
218- scopes = if name . include? ( '::' )
219- RubyLanguageServer ::ScopeData ::Scope . where ( path : name )
220- else
221- RubyLanguageServer ::ScopeData ::Scope . where ( name :)
222- end
223-
224- # Filter by class_method attribute if specified
225- scopes = scopes . where ( class_method : class_method_filter ) unless class_method_filter . nil?
226-
227- variables = RubyLanguageServer ::ScopeData ::Variable . constant_variables . where ( name :)
228- ( scopes + variables ) . reject { |scope | scope . code_file . nil? } . map do |scope |
229- Location . hash ( scope . code_file . uri , scope . top_line , 1 )
242+ # class_method_filter is for new -> initialize
243+ def project_definitions_for ( name , parent_scopes = [ ] , class_method_filter = nil )
244+ results = [ ]
245+
246+ if parent_scopes . empty?
247+ # No parent scopes provided - search all top-level scopes
248+ all_scopes = RubyLanguageServer ::ScopeData ::Scope . where ( name : name )
249+ all_scopes = all_scopes . where ( class_method : class_method_filter ) unless class_method_filter . nil?
250+ results . concat ( all_scopes . to_a )
251+
252+ # Also search for constants at root level
253+ all_variables = RubyLanguageServer ::ScopeData ::Variable . where ( name : name )
254+ results . concat ( all_variables . to_a )
255+ else
256+ # Start with the deepest (first) scope and search upward through parent chain
257+ current_scope = parent_scopes . first
258+ while current_scope
259+ # Search for child scopes with matching name in current scope
260+ child_scopes = current_scope . children . where ( name : name )
261+ child_scopes = child_scopes . where ( class_method : class_method_filter ) unless class_method_filter . nil?
262+ results . concat ( child_scopes . to_a )
263+
264+ # Search for variables with matching name in current scope
265+ matching_variables = current_scope . variables . where ( name : name )
266+ results . concat ( matching_variables . to_a )
267+
268+ # If we found results, stop searching (most specific scope wins)
269+ break unless results . empty?
270+
271+ # Move up to parent scope
272+ current_scope = current_scope . parent
273+ end
274+ end
275+
276+ # Return locations for all matching scopes and variables
277+ results . reject { |item | item . code_file . nil? } . map do |item |
278+ line = item . respond_to? ( :top_line ) ? item . top_line : item . line
279+ Location . hash ( item . code_file . uri , line , 1 )
230280 end
231281 end
232282
233283 private
234284
285+ # Find a scope by its path (e.g., "Foo::Bar")
286+ # Returns nil if path is nil or empty (for root scope searches)
287+ def find_scope_by_path ( path )
288+ return nil if path . nil? || path . empty?
289+
290+ RubyLanguageServer ::ScopeData ::Scope . find_by ( path : path )
291+ end
292+
293+ # Check if a name looks like a constant (all uppercase)
294+ def constant_name? ( name )
295+ # Must start with uppercase letter and contain no lowercase letters
296+ return false unless /\A [A-Z]/ . match? ( name )
297+
298+ !/[a-z]/ . match? ( name )
299+ end
300+
235301 # Check if the context represents a namespace reference (Foo::Bar) rather than a method call (Foo.bar)
236302 # Class/module lookups always start with uppercase letters, method calls never do
237303 def namespace_reference? ( _uri , _position , context )
0 commit comments