@@ -15,6 +15,7 @@ import LSPLogging
15
15
import LanguageServerProtocol
16
16
import SKSupport
17
17
import SourceKitD
18
+ import SwiftSyntax
18
19
19
20
// MARK: - Helper types
20
21
@@ -465,7 +466,7 @@ extension SwiftLanguageServer {
465
466
}
466
467
}
467
468
468
- /// A name that has a represenentation both in Swift and clang-based languages.
469
+ /// A name that has a representation both in Swift and clang-based languages.
469
470
///
470
471
/// These names might differ. For example, an Objective-C method gets translated by the clang importer to form the Swift
471
472
/// name or it could have a `SWIFT_NAME` attribute that defines the method's name in Swift. Similarly, a Swift symbol
@@ -504,7 +505,7 @@ public struct CrossLanguageName {
504
505
505
506
// MARK: - SourceKitServer
506
507
507
- /// The kinds of symbol occurrance roles that should be renamed.
508
+ /// The kinds of symbol occurrence roles that should be renamed.
508
509
fileprivate let renameRoles : SymbolRole = [ . declaration, . definition, . reference]
509
510
510
511
extension DocumentManager {
@@ -747,17 +748,14 @@ extension SourceKitServer {
747
748
workspace: Workspace ,
748
749
languageService: ToolchainLanguageServer
749
750
) async throws -> PrepareRenameResponse ? {
750
- guard var prepareRenameResult = try await languageService. prepareRename ( request) else {
751
+ guard let languageServicePrepareRename = try await languageService. prepareRename ( request) else {
751
752
return nil
752
753
}
753
-
754
- let symbolInfo = try await languageService. symbolInfo (
755
- SymbolInfoRequest ( textDocument: request. textDocument, position: request. position)
756
- )
754
+ var prepareRenameResult = languageServicePrepareRename. prepareRename
757
755
758
756
guard
759
757
let index = workspace. index,
760
- let usr = symbolInfo . only ? . usr,
758
+ let usr = languageServicePrepareRename . usr,
761
759
let oldName = try await self . getCrossLanguageName ( forUsr: usr, workspace: workspace, index: index)
762
760
else {
763
761
return prepareRenameResult
@@ -826,11 +824,169 @@ extension SwiftLanguageServer {
826
824
return categorizedRanges. compactMap { SyntacticRenameName ( $0, in: snapshot, keys: keys, values: values) }
827
825
}
828
826
827
+ /// If `position` is on an argument label or a parameter name, find the position of the function's base name.
828
+ 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
+ let tree = await self . syntaxTreeManager. syntaxTree ( for: snapshot)
869
+ guard let absolutePosition = snapshot. position ( of: position) else {
870
+ return nil
871
+ }
872
+ guard let token = TokenFinder . findToken ( at: absolutePosition, in: tree) else {
873
+ return nil
874
+ }
875
+
876
+ // The node that contains the function's base name. This might be an expression like `self.doStuff`.
877
+ // The start position of the last token in this node will be used as the base name position.
878
+ var baseNode : Syntax ? = nil
879
+
880
+ switch token. keyPathInParent {
881
+ case \LabeledExprSyntax . label:
882
+ let callLike = token. parent ( as: LabeledExprSyntax . self) ? . parent ( as: LabeledExprListSyntax . self) ? . parent
883
+ switch callLike? . as ( SyntaxEnum . self) {
884
+ case . attribute( let attribute) :
885
+ baseNode = Syntax ( attribute. attributeName)
886
+ case . functionCallExpr( let functionCall) :
887
+ baseNode = Syntax ( functionCall. calledExpression)
888
+ case . macroExpansionDecl( let macroExpansionDecl) :
889
+ baseNode = Syntax ( macroExpansionDecl. macroName)
890
+ case . macroExpansionExpr( let macroExpansionExpr) :
891
+ baseNode = Syntax ( macroExpansionExpr. macroName)
892
+ case . subscriptCallExpr( let subscriptCall) :
893
+ baseNode = Syntax ( subscriptCall. leftSquare)
894
+ default :
895
+ break
896
+ }
897
+ case \FunctionParameterSyntax . firstName:
898
+ let parameterClause =
899
+ token
900
+ . parent ( as: FunctionParameterSyntax . self) ?
901
+ . parent ( as: FunctionParameterListSyntax . self) ?
902
+ . parent ( as: FunctionParameterClauseSyntax . self)
903
+ if let functionSignature = parameterClause? . parent ( as: FunctionSignatureSyntax . self) {
904
+ switch functionSignature. parent? . as ( SyntaxEnum . self) {
905
+ case . functionDecl( let functionDecl) :
906
+ baseNode = Syntax ( functionDecl. name)
907
+ case . initializerDecl( let initializerDecl) :
908
+ baseNode = Syntax ( initializerDecl. initKeyword)
909
+ case . macroDecl( let macroDecl) :
910
+ baseNode = Syntax ( macroDecl. name)
911
+ default :
912
+ break
913
+ }
914
+ } else if let subscriptDecl = parameterClause? . parent ( as: SubscriptDeclSyntax . self) {
915
+ baseNode = Syntax ( subscriptDecl. subscriptKeyword)
916
+ }
917
+ case \DeclNameArgumentSyntax . name:
918
+ let declReference =
919
+ token
920
+ . parent ( as: DeclNameArgumentSyntax . self) ?
921
+ . parent ( as: DeclNameArgumentListSyntax . self) ?
922
+ . parent ( as: DeclNameArgumentsSyntax . self) ?
923
+ . parent ( as: DeclReferenceExprSyntax . self)
924
+ baseNode = Syntax ( declReference? . baseName)
925
+ default :
926
+ break
927
+ }
928
+
929
+ if let lastToken = baseNode? . lastToken ( viewMode: . sourceAccurate) ,
930
+ let position = snapshot. position ( of: lastToken. positionAfterSkippingLeadingTrivia)
931
+ {
932
+ return position
933
+ }
934
+ return nil
935
+ }
936
+
937
+ /// When the user requested a rename at `position` in `snapshot`, determine the position at which the rename should be
938
+ /// performed internally and USR of the symbol to rename.
939
+ ///
940
+ /// This is necessary to adjust the rename position when renaming function parameters. For example when invoking
941
+ /// rename on `x` in `foo(x:)`, we need to perform a rename of `foo` in sourcekitd so that we can rename the function
942
+ /// parameter.
943
+ ///
944
+ /// The position might be `nil` if there is no local position in the file that refers to the base name to be renamed.
945
+ /// This happens if renaming a function parameter of `MyStruct(x:)` where `MyStruct` is defined outside of the current
946
+ /// file. In this case, there is no base name that refers to the initializer of `MyStruct`. When `position` is `nil`
947
+ /// a pure index-based rename from the usr USR or `symbolDetails` needs to be performed and no `relatedIdentifiers`
948
+ /// request can be used to rename symbols in the current file.
949
+ func symbolToRename(
950
+ at position: Position ,
951
+ in snapshot: DocumentSnapshot
952
+ ) async -> ( position: Position ? , usr: String ? ) {
953
+ let symbolInfo = try ? await self . symbolInfo (
954
+ SymbolInfoRequest ( textDocument: TextDocumentIdentifier ( snapshot. uri) , position: position)
955
+ )
956
+
957
+ guard let baseNamePosition = await findFunctionBaseNamePosition ( of: position, in: snapshot) else {
958
+ return ( position, symbolInfo? . only? . usr)
959
+ }
960
+ if let onlySymbol = symbolInfo? . only, onlySymbol. kind == . constructor {
961
+ // We have a rename like `MyStruct(x: 1)`, invoked from `x`.
962
+ if let bestLocalDeclaration = onlySymbol. bestLocalDeclaration, bestLocalDeclaration. uri == snapshot. uri {
963
+ // If the initializer is declared within the same file, we can perform rename in the current file based on
964
+ // the declaration's location.
965
+ return ( bestLocalDeclaration. range. lowerBound, onlySymbol. usr)
966
+ }
967
+ // Otherwise, we don't have a reference to the base name of the initializer and we can't use related
968
+ // identifiers to perform the rename.
969
+ // Return `nil` for the position to perform a pure index-based rename.
970
+ return ( nil , onlySymbol. usr)
971
+ }
972
+ // Adjust the symbol info to the symbol info of the base name.
973
+ // This ensures that we get the symbol info of the function's base instead of the parameter.
974
+ let baseNameSymbolInfo = try ? await self . symbolInfo (
975
+ SymbolInfoRequest ( textDocument: TextDocumentIdentifier ( snapshot. uri) , position: baseNamePosition)
976
+ )
977
+ return ( baseNamePosition, baseNameSymbolInfo? . only? . usr)
978
+ }
979
+
829
980
public func rename( _ request: RenameRequest ) async throws -> ( edits: WorkspaceEdit , usr: String ? ) {
830
981
let snapshot = try self . documentManager. latestSnapshot ( request. textDocument. uri)
831
982
983
+ let ( renamePosition, usr) = await symbolToRename ( at: request. position, in: snapshot)
984
+ guard let renamePosition else {
985
+ return ( edits: WorkspaceEdit ( ) , usr: usr)
986
+ }
987
+
832
988
let relatedIdentifiersResponse = try await self . relatedIdentifiers (
833
- at: request . position ,
989
+ at: renamePosition ,
834
990
in: snapshot,
835
991
includeNonEditableBaseNames: true
836
992
)
@@ -862,10 +1018,6 @@ extension SwiftLanguageServer {
862
1018
863
1019
try Task . checkCancellation ( )
864
1020
865
- let usr =
866
- ( try ? await self . symbolInfo ( SymbolInfoRequest ( textDocument: request. textDocument, position: request. position) ) ) ?
867
- . only? . usr
868
-
869
1021
return ( edits: WorkspaceEdit ( changes: [ snapshot. uri: edits] ) , usr: usr)
870
1022
}
871
1023
@@ -998,25 +1150,29 @@ extension SwiftLanguageServer {
998
1150
}
999
1151
}
1000
1152
1001
- public func prepareRename( _ request: PrepareRenameRequest ) async throws -> PrepareRenameResponse ? {
1153
+ public func prepareRename(
1154
+ _ request: PrepareRenameRequest
1155
+ ) async throws -> ( prepareRename: PrepareRenameResponse , usr: String ? ) ? {
1002
1156
let snapshot = try self . documentManager. latestSnapshot ( request. textDocument. uri)
1003
1157
1158
+ let ( renamePosition, usr) = await symbolToRename ( at: request. position, in: snapshot)
1159
+ guard let renamePosition else {
1160
+ return nil
1161
+ }
1162
+
1004
1163
let response = try await self . relatedIdentifiers (
1005
- at: request . position ,
1164
+ at: renamePosition ,
1006
1165
in: snapshot,
1007
1166
includeNonEditableBaseNames: true
1008
1167
)
1009
1168
guard let name = response. name else {
1010
1169
throw ResponseError . unknown ( " Running sourcekit-lsp with a version of sourcekitd that does not support rename " )
1011
1170
}
1012
- guard let range = response. relatedIdentifiers. first ( where: { $0. range. contains ( request . position ) } ) ? . range
1171
+ guard let range = response. relatedIdentifiers. first ( where: { $0. range. contains ( renamePosition ) } ) ? . range
1013
1172
else {
1014
1173
return nil
1015
1174
}
1016
- return PrepareRenameResponse (
1017
- range: range,
1018
- placeholder: name
1019
- )
1175
+ return ( PrepareRenameResponse ( range: range, placeholder: name) , usr)
1020
1176
}
1021
1177
}
1022
1178
@@ -1065,7 +1221,22 @@ extension ClangLanguageServerShim {
1065
1221
}
1066
1222
}
1067
1223
1068
- public func prepareRename( _ request: PrepareRenameRequest ) async throws -> PrepareRenameResponse ? {
1069
- return try await forwardRequestToClangd ( request)
1224
+ public func prepareRename(
1225
+ _ request: PrepareRenameRequest
1226
+ ) async throws -> ( prepareRename: PrepareRenameResponse , usr: String ? ) ? {
1227
+ guard let prepareRename = try await forwardRequestToClangd ( request) else {
1228
+ return nil
1229
+ }
1230
+ let symbolInfo = try await forwardRequestToClangd (
1231
+ SymbolInfoRequest ( textDocument: request. textDocument, position: request. position)
1232
+ )
1233
+ return ( prepareRename, symbolInfo. only? . usr)
1234
+ }
1235
+ }
1236
+
1237
+ fileprivate extension SyntaxProtocol {
1238
+ /// Returns the parent node and casts it to the specified type.
1239
+ func parent< S: SyntaxProtocol > ( as syntaxType: S . Type ) -> S ? {
1240
+ return parent? . as ( S . self)
1070
1241
}
1071
1242
}
0 commit comments