@@ -160,7 +160,7 @@ def word_at_location(uri, position)
160160
161161 def possible_definitions ( uri , position )
162162 name = word_at_location ( uri , position )
163- return { } if name . blank?
163+ return [ ] if name . blank?
164164
165165 # Special case: "new" should find "initialize" (instance method, not class method)
166166 name = 'initialize' if name == 'new'
@@ -230,60 +230,91 @@ def possible_definitions(uri, position)
230230 results = scope_definitions_for ( name , scope , uri )
231231 return results unless results . empty?
232232
233- project_definitions_for ( name , current_scopes )
233+ # Always return an array
234+ project_definitions_for ( name , current_scopes ) || [ ]
234235 end
235236
236237 # Return variables found in the current scope. After all, those are the important ones.
237238 # Should probably be private...
238239 def scope_definitions_for ( name , scope , uri )
239240 check_scope = scope
240- return_array = [ ]
241+ matches = [ ]
241242 while check_scope
242243 check_scope . variables . each do |variable |
243- return_array << Location . hash ( uri , variable . line , 1 ) if variable . name == name
244+ matches << Location . hash ( uri , variable . line , 1 ) if variable . name == name
244245 end
245246 check_scope = check_scope . parent
246247 end
247- RubyLanguageServer . logger . debug ( "==============>> scope_definitions_for(#{ name } , #{ scope . to_json } , #{ uri } : #{ return_array . uniq } )" )
248- return_array . uniq
248+ RubyLanguageServer . logger . debug ( "==============>> scope_definitions_for(#{ name } , #{ scope . to_json } , #{ uri } : #{ matches . inspect } )" )
249+ matches
249250 end
250251
251252 # class_method_filter is for new -> initialize
252253 def project_definitions_for ( name , parent_scopes = [ ] , class_method_filter = nil )
253- results = [ ]
254-
254+ # If no parent scope, just return all matches as before
255255 if parent_scopes . empty?
256- # No parent scopes provided - search all top-level scopes
257256 all_scopes = RubyLanguageServer ::ScopeData ::Scope . where ( name : name )
258257 all_scopes = all_scopes . where ( class_method : class_method_filter ) unless class_method_filter . nil?
259- results . concat ( all_scopes . to_a )
260-
261- # Also search for constants at root level
262258 all_variables = RubyLanguageServer ::ScopeData ::Variable . where ( name : name )
263- results . concat ( all_variables . to_a )
264- else
265- # Start with the deepest (first) scope and search upward through parent chain
266- current_scope = parent_scopes . first
267- while current_scope
268- # Search for child scopes with matching name in current scope
269- child_scopes = current_scope . children . where ( name : name )
270- child_scopes = child_scopes . where ( class_method : class_method_filter ) unless class_method_filter . nil?
271- results . concat ( child_scopes . to_a )
272-
273- # Search for variables with matching name in current scope
274- matching_variables = current_scope . variables . where ( name : name )
275- results . concat ( matching_variables . to_a )
276-
277- # If we found results, stop searching (most specific scope wins)
278- break unless results . empty?
279-
280- # Move up to parent scope
281- current_scope = current_scope . parent
259+ all_matches = all_scopes . to_a + all_variables . to_a
260+ return all_matches . reject { |item | item . code_file . nil? } . map do |item |
261+ line = item . respond_to? ( :top_line ) ? item . top_line : item . line
262+ Location . hash ( item . code_file . uri , line , 1 )
263+ end || [ ]
264+ end
265+
266+ # First pass: Walk up the parent scope chain, looking ONLY for variables (closest wins)
267+ # Variables in scope chain take priority over child scopes and project-wide definitions
268+ supplied_scope = parent_scopes . first
269+
270+ current = supplied_scope
271+ while current
272+ # For constants, skip method scopes and keep searching up to class/module scopes
273+ if current . class_type == RubyLanguageServer ::ScopeData ::Scope ::TYPE_METHOD
274+ current = current . parent
275+ next
276+ end
277+
278+ # Check variables (constants) in the current scope
279+ matching_variable = current . variables . where ( name : name ) . reject { |item | item . code_file . nil? } . first
280+ if matching_variable
281+ line = matching_variable . respond_to? ( :top_line ) ? matching_variable . top_line : matching_variable . line
282+ return [ Location . hash ( matching_variable . code_file . uri , line , 1 ) ]
282283 end
284+
285+ # Move up to parent scope and continue searching for variables
286+ current = current . parent
287+ end
288+
289+ # Second pass: If no variables found, check child scopes in the scope chain
290+ current = supplied_scope
291+ while current
292+ # For constants, skip method scopes and keep searching up to class/module scopes
293+ if current . class_type == RubyLanguageServer ::ScopeData ::Scope ::TYPE_METHOD
294+ current = current . parent
295+ next
296+ end
297+
298+ # Check child scopes (classes/modules/methods) with this name (for class/module/method lookup)
299+ child_scopes = current . children . where ( name : name )
300+ child_scopes = child_scopes . where ( class_method : class_method_filter ) unless class_method_filter . nil?
301+ unless child_scopes . empty?
302+ return child_scopes . reject { |item | item . code_file . nil? } . map do |item |
303+ line = item . respond_to? ( :top_line ) ? item . top_line : item . line
304+ Location . hash ( item . code_file . uri , line , 1 )
305+ end
306+ end
307+
308+ # Move up to parent scope and continue searching
309+ current = current . parent
283310 end
284311
285- # Return locations for all matching scopes and variables
286- results . reject { |item | item . code_file . nil? } . map do |item |
312+ # If nothing found in the scope chain, fall back to project-wide search
313+ all_scopes = RubyLanguageServer ::ScopeData ::Scope . where ( name : name )
314+ all_scopes = all_scopes . where ( class_method : class_method_filter ) unless class_method_filter . nil?
315+ all_variables = RubyLanguageServer ::ScopeData ::Variable . where ( name : name )
316+ all_matches = all_scopes . to_a + all_variables . to_a
317+ all_matches . reject { |item | item . code_file . nil? } . map do |item |
287318 line = item . respond_to? ( :top_line ) ? item . top_line : item . line
288319 Location . hash ( item . code_file . uri , line , 1 )
289320 end
0 commit comments