@@ -284,14 +284,6 @@ private extension DocumentSnapshot {
284
284
let contents = try String ( contentsOf: url)
285
285
self . init ( uri: DocumentURI ( url) , language: language, version: 0 , lineTable: LineTable ( contents) )
286
286
}
287
-
288
- func position( of renameLocation: RenameLocation ) -> Position ? {
289
- return positionOf ( zeroBasedLine: renameLocation. line - 1 , utf8Column: renameLocation. utf8Column - 1 )
290
- }
291
-
292
- func position( of symbolLocation: SymbolLocation ) -> Position ? {
293
- return positionOf ( zeroBasedLine: symbolLocation. line - 1 , utf8Column: symbolLocation. utf8Column - 1 )
294
- }
295
287
}
296
288
297
289
private extension RenameLocation . Usage {
@@ -713,21 +705,27 @@ extension SourceKitServer {
713
705
logger. error ( " Failed to get document snapshot for \( uri. forLogging) " )
714
706
return nil
715
707
}
716
- do {
717
- guard let languageService = await self . languageService ( for: uri, language, in: workspace) else {
718
- return nil
719
- }
720
- let edits = try await languageService. editsToRename (
721
- locations: renameLocations,
722
- in: snapshot,
723
- oldName: oldName,
708
+ guard let languageService = await self . languageService ( for: uri, language, in: workspace) else {
709
+ return nil
710
+ }
711
+
712
+ var edits : [ TextEdit ] =
713
+ await orLog ( " Getting edits for rename location " ) {
714
+ return try await languageService. editsToRename (
715
+ locations: renameLocations,
716
+ in: snapshot,
717
+ oldName: oldName,
718
+ newName: newName
719
+ )
720
+ } ?? [ ]
721
+ for location in renameLocations where location. usage == . definition {
722
+ edits += await languageService. editsToRenameParametersInFunctionBody (
723
+ snapshot: snapshot,
724
+ renameLocation: location,
724
725
newName: newName
725
726
)
726
- return ( uri, edits)
727
- } catch {
728
- logger. error ( " Failed to get edits for \( uri. forLogging) : \( error. forLogging) " )
729
- return nil
730
727
}
728
+ return ( uri, edits)
731
729
} . compactMap { $0 }
732
730
for (uri, editsForUri) in urisAndEdits {
733
731
precondition (
@@ -826,50 +824,11 @@ extension SwiftLanguageServer {
826
824
827
825
/// If `position` is on an argument label or a parameter name, find the position of the function's base name.
828
826
private func findFunctionBaseNamePosition( of position: Position , in snapshot: DocumentSnapshot ) async -> Position ? {
829
- class TokenFinder : SyntaxAnyVisitor {
830
- /// The position at which the token should be found.
831
- let position : AbsolutePosition
832
-
833
- /// Once found, the token at the requested position.
834
- var foundToken : TokenSyntax ?
835
-
836
- init ( position: AbsolutePosition ) {
837
- self . position = position
838
- super. init ( viewMode: . sourceAccurate)
839
- }
840
-
841
- override func visitAny( _ node: Syntax ) -> SyntaxVisitorContinueKind {
842
- guard ( node. position..< node. endPosition) . contains ( position) else {
843
- // Node doesn't contain the position. No point visiting it.
844
- return . skipChildren
845
- }
846
- guard foundToken == nil else {
847
- // We have already found a token. No point visiting this one
848
- return . skipChildren
849
- }
850
- return . visitChildren
851
- }
852
-
853
- override func visit( _ token: TokenSyntax ) -> SyntaxVisitorContinueKind {
854
- if ( token. position..< token. endPosition) . contains ( position) {
855
- self . foundToken = token
856
- }
857
- return . skipChildren
858
- }
859
-
860
- /// Dedicated entry point for `TokenFinder`.
861
- static func findToken( at position: AbsolutePosition , in tree: some SyntaxProtocol ) -> TokenSyntax ? {
862
- let finder = TokenFinder ( position: position)
863
- finder. walk ( tree)
864
- return finder. foundToken
865
- }
866
- }
867
-
868
827
let tree = await self . syntaxTreeManager. syntaxTree ( for: snapshot)
869
- guard let absolutePosition = snapshot. position ( of: position) else {
828
+ guard let absolutePosition = snapshot. absolutePosition ( of: position) else {
870
829
return nil
871
830
}
872
- guard let token = TokenFinder . findToken ( at: absolutePosition, in : tree ) else {
831
+ guard let token = tree . token ( at: absolutePosition) else {
873
832
return nil
874
833
}
875
834
@@ -990,37 +949,140 @@ extension SwiftLanguageServer {
990
949
in: snapshot,
991
950
includeNonEditableBaseNames: true
992
951
)
993
- guard let oldName = relatedIdentifiersResponse. name else {
952
+ guard let oldNameString = relatedIdentifiersResponse. name else {
994
953
throw ResponseError . unknown ( " Running sourcekit-lsp with a version of sourcekitd that does not support rename " )
995
954
}
996
955
997
- try Task . checkCancellation ( )
998
-
999
- let renameLocations = relatedIdentifiersResponse. relatedIdentifiers. compactMap {
1000
- ( relatedIdentifier) -> RenameLocation ? in
1001
- let position = relatedIdentifier. range. lowerBound
1002
- guard let utf8Column = snapshot. lineTable. utf8ColumnAt ( line: position. line, utf16Column: position. utf16index)
1003
- else {
1004
- logger. fault ( " Unable to find UTF-8 column for \( position. line) : \( position. utf16index) " )
1005
- return nil
1006
- }
1007
- return RenameLocation ( line: position. line + 1 , utf8Column: utf8Column + 1 , usage: relatedIdentifier. usage)
1008
- }
956
+ let renameLocations = relatedIdentifiersResponse. renameLocations ( in: snapshot)
1009
957
1010
958
try Task . checkCancellation ( )
1011
959
1012
- let edits = try await editsToRename (
960
+ let oldName = CrossLanguageName ( clangName: nil , swiftName: oldNameString, definitionLanguage: . swift)
961
+ let newName = CrossLanguageName ( clangName: nil , swiftName: request. newName, definitionLanguage: . swift)
962
+ var edits = try await editsToRename (
1013
963
locations: renameLocations,
1014
964
in: snapshot,
1015
- oldName: CrossLanguageName ( clangName : nil , swiftName : oldName, definitionLanguage : . swift ) ,
1016
- newName: CrossLanguageName ( clangName : nil , swiftName : request . newName, definitionLanguage : . swift )
965
+ oldName: oldName,
966
+ newName: newName
1017
967
)
1018
-
1019
- try Task . checkCancellation ( )
968
+ if let compoundSwiftName = oldName. compoundSwiftName, !compoundSwiftName. parameters. isEmpty {
969
+ // If we are doing a function rename, run `renameParametersInFunctionBody` for every occurrence of the rename
970
+ // location within the current file. If the location is not a function declaration, it will exit early without
971
+ // invoking sourcekitd, so it's OK to do this performance-wise.
972
+ for renameLocation in renameLocations {
973
+ edits += await editsToRenameParametersInFunctionBody (
974
+ snapshot: snapshot,
975
+ renameLocation: renameLocation,
976
+ newName: newName
977
+ )
978
+ }
979
+ }
1020
980
1021
981
return ( edits: WorkspaceEdit ( changes: [ snapshot. uri: edits] ) , usr: usr)
1022
982
}
1023
983
984
+ public func editsToRenameParametersInFunctionBody(
985
+ snapshot: DocumentSnapshot ,
986
+ renameLocation: RenameLocation ,
987
+ newName: CrossLanguageName
988
+ ) async -> [ TextEdit ] {
989
+ guard let position = snapshot. absolutePosition ( of: renameLocation) else {
990
+ logger. fault ( " Failed to convert \( renameLocation. line) : \( renameLocation. utf8Column) to AbsolutePosition " )
991
+ return [ ]
992
+ }
993
+ let syntaxTree = await syntaxTreeManager. syntaxTree ( for: snapshot)
994
+ let token = syntaxTree. token ( at: position)
995
+ let parameterClause : FunctionParameterClauseSyntax ?
996
+ switch token? . keyPathInParent {
997
+ case \FunctionDeclSyntax . name:
998
+ parameterClause = token? . parent ( as: FunctionDeclSyntax . self) ? . signature. parameterClause
999
+ case \InitializerDeclSyntax . initKeyword:
1000
+ parameterClause = token? . parent ( as: InitializerDeclSyntax . self) ? . signature. parameterClause
1001
+ case \SubscriptDeclSyntax . subscriptKeyword:
1002
+ parameterClause = token? . parent ( as: SubscriptDeclSyntax . self) ? . parameterClause
1003
+ default :
1004
+ parameterClause = nil
1005
+ }
1006
+ guard let parameterClause else {
1007
+ // We are not at a function-like definition. Nothing to rename.
1008
+ return [ ]
1009
+ }
1010
+ guard let newSwiftNameString = newName. swiftName else {
1011
+ logger. fault (
1012
+ " Cannot rename at \( renameLocation. line) : \( renameLocation. utf8Column) because new name is not a Swift name "
1013
+ )
1014
+ return [ ]
1015
+ }
1016
+ let newSwiftName = CompoundDeclName ( newSwiftNameString)
1017
+
1018
+ var edits : [ TextEdit ] = [ ]
1019
+ for (index, parameter) in parameterClause. parameters. enumerated ( ) {
1020
+ guard parameter. secondName == nil else {
1021
+ // The parameter has a second name. The function signature only renames the first name and the function body
1022
+ // refers to the second name. Nothing to do.
1023
+ continue
1024
+ }
1025
+ let oldParameterName = parameter. firstName. text
1026
+ guard index < newSwiftName. parameters. count else {
1027
+ // We don't have a new name for this parameter. Nothing to do.
1028
+ continue
1029
+ }
1030
+ let newParameterName = newSwiftName. parameters [ index] . stringOrEmpty
1031
+ guard !newParameterName. isEmpty else {
1032
+ // We are changing the parameter to an empty name. This will retain the current external parameter name as the
1033
+ // new second name, so nothing to do in the function body.
1034
+ continue
1035
+ }
1036
+ guard newParameterName != oldParameterName else {
1037
+ // This parameter wasn't modified. Nothing to do.
1038
+ continue
1039
+ }
1040
+
1041
+ let oldCrossLanguageParameterName = CrossLanguageName (
1042
+ clangName: nil ,
1043
+ swiftName: oldParameterName,
1044
+ definitionLanguage: . swift
1045
+ )
1046
+ let newCrossLanguageParameterName = CrossLanguageName (
1047
+ clangName: nil ,
1048
+ swiftName: newParameterName,
1049
+ definitionLanguage: . swift
1050
+ )
1051
+
1052
+ guard let parameterPosition = snapshot. position ( of: parameter. positionAfterSkippingLeadingTrivia) else {
1053
+ logger. fault ( " Failed to convert position of \( parameter. firstName. text) to line-column " )
1054
+ continue
1055
+ }
1056
+
1057
+ let parameterRenameEdits = await orLog ( " Renaming parameter " ) {
1058
+ // Once we have lexical scope lookup in swift-syntax, this can be a purely syntactic rename.
1059
+ // We know that the parameters are variables and thus there can't be overloads that need to be resolved by the
1060
+ // type checker.
1061
+ let relatedIdentifiersResponse = try await self . relatedIdentifiers (
1062
+ at: parameterPosition,
1063
+ in: snapshot,
1064
+ includeNonEditableBaseNames: false
1065
+ )
1066
+
1067
+ let parameterRenameLocations = relatedIdentifiersResponse. renameLocations ( in: snapshot)
1068
+
1069
+ return try await editsToRename (
1070
+ locations: parameterRenameLocations,
1071
+ in: snapshot,
1072
+ oldName: oldCrossLanguageParameterName,
1073
+ newName: newCrossLanguageParameterName
1074
+ )
1075
+ }
1076
+ guard let parameterRenameEdits else {
1077
+ continue
1078
+ }
1079
+ // Exclude the edit that renames the parameter itself. The parameter gets renamed as part of the function
1080
+ // declaration.
1081
+ edits += parameterRenameEdits. filter { !$0. range. contains ( parameterPosition) }
1082
+ }
1083
+ return edits
1084
+ }
1085
+
1024
1086
/// Return the edit that needs to be performed for the given syntactic rename piece to rename it from
1025
1087
/// `oldParameter` to `newParameter`.
1026
1088
/// Returns `nil` if no edit needs to be performed.
@@ -1232,6 +1294,15 @@ extension ClangLanguageServerShim {
1232
1294
)
1233
1295
return ( prepareRename, symbolInfo. only? . usr)
1234
1296
}
1297
+
1298
+ public func editsToRenameParametersInFunctionBody(
1299
+ snapshot: DocumentSnapshot ,
1300
+ renameLocation: RenameLocation ,
1301
+ newName: CrossLanguageName
1302
+ ) async -> [ TextEdit ] {
1303
+ // When renaming a clang function name, we don't need to rename any references to the arguments.
1304
+ return [ ]
1305
+ }
1235
1306
}
1236
1307
1237
1308
fileprivate extension SyntaxProtocol {
@@ -1240,3 +1311,18 @@ fileprivate extension SyntaxProtocol {
1240
1311
return parent? . as ( S . self)
1241
1312
}
1242
1313
}
1314
+
1315
+ fileprivate extension RelatedIdentifiersResponse {
1316
+ func renameLocations( in snapshot: DocumentSnapshot ) -> [ RenameLocation ] {
1317
+ return self . relatedIdentifiers. compactMap {
1318
+ ( relatedIdentifier) -> RenameLocation ? in
1319
+ let position = relatedIdentifier. range. lowerBound
1320
+ guard let utf8Column = snapshot. lineTable. utf8ColumnAt ( line: position. line, utf16Column: position. utf16index)
1321
+ else {
1322
+ logger. fault ( " Unable to find UTF-8 column for \( position. description, privacy: . public) " )
1323
+ return nil
1324
+ }
1325
+ return RenameLocation ( line: position. line + 1 , utf8Column: utf8Column + 1 , usage: relatedIdentifier. usage)
1326
+ }
1327
+ }
1328
+ }
0 commit comments