@@ -22,6 +22,7 @@ def provider
2222 def initialize ( global_state , store , document , params )
2323 super ( )
2424 @global_state = global_state
25+ @graph = global_state . graph #: Rubydex::Graph
2526 @store = store
2627 @document = document
2728 @position = params [ :position ] #: Hash[Symbol, Integer]
@@ -56,17 +57,14 @@ def perform
5657 name = RubyIndexer ::Index . constant_name ( target )
5758 return unless name
5859
59- entries = @global_state . index . resolve ( name , node_context . nesting )
60- return unless entries
60+ declaration = @graph . resolve_constant ( name , node_context . nesting )
61+ return unless declaration
6162
62- if ( conflict_entries = @global_state . index . resolve ( @new_name , node_context . nesting ) )
63- raise InvalidNameError , "The new name is already in use by #{ conflict_entries . first & .name } "
63+ if ( conflict = @graph . resolve_constant ( @new_name , node_context . nesting ) )
64+ raise InvalidNameError , "The new name is already in use by #{ conflict . name } "
6465 end
6566
66- fully_qualified_name = entries . first #: as !nil
67- . name
68- reference_target = RubyIndexer ::ReferenceFinder ::ConstTarget . new ( fully_qualified_name )
69- changes = collect_text_edits ( reference_target , name )
67+ changes = collect_text_edits ( declaration , name )
7068
7169 # If the client doesn't support resource operations, such as renaming files, then we can only return the basic
7270 # text changes
@@ -78,99 +76,93 @@ def perform
7876 # renamed and then the URI associated to the text edit no longer exists, causing it to be dropped
7977 document_changes = changes . map do |uri , edits |
8078 Interface ::TextDocumentEdit . new (
81- text_document : Interface ::VersionedTextDocumentIdentifier . new ( uri : uri , version : nil ) ,
79+ text_document : Interface ::OptionalVersionedTextDocumentIdentifier . new ( uri : uri , version : nil ) ,
8280 edits : edits ,
8381 )
8482 end
8583
86- collect_file_renames ( fully_qualified_name , document_changes )
84+ collect_file_renames ( declaration , document_changes )
8785 Interface ::WorkspaceEdit . new ( document_changes : document_changes )
8886 end
8987
9088 private
9189
92- #: (String fully_qualified_name , Array[(Interface::RenameFile | Interface::TextDocumentEdit)] document_changes ) -> void
93- def collect_file_renames ( fully_qualified_name , document_changes )
90+ #: (Rubydex::Declaration , Array[(Interface::RenameFile | Interface::TextDocumentEdit)]) -> void
91+ def collect_file_renames ( declaration , document_changes )
9492 # Check if the declarations of the symbol being renamed match the file name. In case they do, we automatically
9593 # rename the files for the user.
9694 #
9795 # We also look for an associated test file and rename it too
98- short_name = fully_qualified_name . split ( "::" ) . last #: as !nil
9996
100- @global_state . index [ fully_qualified_name ] &.each do |entry |
97+ unless [
98+ Rubydex ::Class ,
99+ Rubydex ::Module ,
100+ Rubydex ::Constant ,
101+ Rubydex ::ConstantAlias ,
102+ ] . any? { |type | declaration . is_a? ( type ) }
103+ return
104+ end
105+
106+ short_name = declaration . unqualified_name
107+
108+ declaration . definitions . each do |definition |
101109 # Do not rename files that are not part of the workspace
102- uri = entry . uri
110+ uri = URI ( definition . location . uri )
103111 file_path = uri . full_path
104112 next unless file_path &.start_with? ( @global_state . workspace_path )
105113
106- case entry
107- when RubyIndexer ::Entry ::Class , RubyIndexer ::Entry ::Module , RubyIndexer ::Entry ::Constant ,
108- RubyIndexer ::Entry ::ConstantAlias , RubyIndexer ::Entry ::UnresolvedConstantAlias
109-
110- file_name = file_from_constant_name ( short_name )
114+ file_name = file_from_constant_name ( short_name )
115+ next unless "#{ file_name } .rb" == File . basename ( file_path )
111116
112- if "#{ file_name } .rb" == entry . file_name
113- new_file_name = file_from_constant_name (
114- @new_name . split ( "::" ) . last , #: as !nil
115- )
117+ new_file_name = file_from_constant_name (
118+ @new_name . split ( "::" ) . last , #: as !nil
119+ )
116120
117- new_uri = URI ::Generic . from_path ( path : File . join (
118- File . dirname ( file_path ) ,
119- "#{ new_file_name } .rb" ,
120- ) ) . to_s
121+ new_uri = URI ::Generic . from_path ( path : File . join (
122+ File . dirname ( file_path ) ,
123+ "#{ new_file_name } .rb" ,
124+ ) ) . to_s
121125
122- document_changes << Interface ::RenameFile . new ( kind : "rename" , old_uri : uri . to_s , new_uri : new_uri )
123- end
124- end
126+ document_changes << Interface ::RenameFile . new ( kind : "rename" , old_uri : uri . to_s , new_uri : new_uri )
125127 end
126128 end
127129
128- #: (RubyIndexer::ReferenceFinder::Target target, String name) -> Hash[String, Array[Interface::TextEdit]]
129- def collect_text_edits ( target , name )
130- changes = { }
131-
132- Dir . glob ( File . join ( @global_state . workspace_path , "**/*.rb" ) ) . each do |path |
133- uri = URI ::Generic . from_path ( path : path )
134- # If the document is being managed by the client, then we should use whatever is present in the store instead
135- # of reading from disk
136- next if @store . key? ( uri )
137-
138- parse_result = Prism . parse_file ( path )
139- edits = collect_changes ( target , parse_result . value , name , uri )
140- changes [ uri . to_s ] = edits unless edits . empty?
141- rescue Errno ::EISDIR , Errno ::ENOENT
142- # If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it.
143- end
144-
145- @store . each do |uri , document |
146- next unless document . is_a? ( RubyDocument ) || document . is_a? ( ERBDocument )
130+ #: (Rubydex::Declaration declaration, String name) -> Hash[String, Array[Interface::TextEdit]]
131+ def collect_text_edits ( declaration , name )
132+ changes = { } #: Hash[String, Array[Interface::TextEdit]]
133+ short_name = name . split ( "::" ) . last #: as !nil
134+ new_short_name = @new_name . split ( "::" ) . last #: as !nil
135+
136+ # Collect edits for definition sites (where the constant is declared)
137+ declaration . definitions . each do |definition |
138+ name_loc = definition . name_location
139+ next unless name_loc
140+
141+ uri_string = name_loc . uri
142+ edits = ( changes [ uri_string ] ||= [ ] )
143+
144+ # The name_location spans the constant name as written in the definition.
145+ # We only replace the unqualified name portion (the last segment).
146+ range = Interface ::Range . new (
147+ start : Interface ::Position . new (
148+ line : name_loc . end_line ,
149+ character : name_loc . end_column - short_name . length ,
150+ ) ,
151+ end : Interface ::Position . new ( line : name_loc . end_line , character : name_loc . end_column ) ,
152+ )
147153
148- edits = collect_changes ( target , document . ast , name , document . uri )
149- changes [ uri ] = edits unless edits . empty?
154+ edits << Interface ::TextEdit . new ( range : range , new_text : new_short_name )
150155 end
151156
152- changes
153- end
154-
155- #: (RubyIndexer::ReferenceFinder::Target target, Prism::Node ast, String name, URI::Generic uri) -> Array[Interface::TextEdit]
156- def collect_changes ( target , ast , name , uri )
157- dispatcher = Prism ::Dispatcher . new
158- finder = RubyIndexer ::ReferenceFinder . new ( target , @global_state . index , dispatcher , uri )
159- dispatcher . visit ( ast )
160-
161- finder . references . map do |reference |
162- adjust_reference_for_edit ( name , reference )
157+ # Collect edits for reference sites (where the constant is used)
158+ declaration . references . each do |reference |
159+ ref = reference #: as Rubydex::ConstantReference
160+ uri_string = ref . location . uri
161+ edits = ( changes [ uri_string ] ||= [ ] )
162+ edits << Interface ::TextEdit . new ( range : ref . to_lsp_range , new_text : new_short_name )
163163 end
164- end
165-
166- #: (String name, RubyIndexer::ReferenceFinder::Reference reference) -> Interface::TextEdit
167- def adjust_reference_for_edit ( name , reference )
168- # The reference may include a namespace in front. We need to check if the rename new name includes namespaces
169- # and then adjust both the text and the location to produce the correct edit
170- location = reference . location
171- new_text = reference . name . sub ( name , @new_name )
172164
173- Interface :: TextEdit . new ( range : range_from_location ( location ) , new_text : new_text )
165+ changes
174166 end
175167
176168 #: (String constant_name) -> String
0 commit comments