@@ -978,143 +978,200 @@ extension SwiftLanguageServer {
978
978
}
979
979
980
980
public func foldingRange( _ req: Request < FoldingRangeRequest > ) {
981
- let keys = self . keys
982
-
983
981
queue. async {
984
982
guard let snapshot = self . documentManager. latestSnapshot ( req. params. textDocument. uri) else {
985
983
log ( " failed to find snapshot for url \( req. params. textDocument. uri) " )
986
984
req. reply ( nil )
987
985
return
988
986
}
989
987
990
- let helperDocumentName = " FoldingRanges: " + snapshot. document. uri. pseudoPath
991
- let skreq = SKDRequestDictionary ( sourcekitd: self . sourcekitd)
992
- skreq [ keys. request] = self . requests. editor_open
993
- skreq [ keys. name] = helperDocumentName
994
- skreq [ keys. sourcetext] = snapshot. text
995
- skreq [ keys. syntactic_only] = 1
988
+ guard let sourceFile = snapshot. tokens. syntaxTree else {
989
+ log ( " no lexical structure available for url \( req. params. textDocument. uri) " )
990
+ req. reply ( nil )
991
+ return
992
+ }
996
993
997
- let handle = self . sourcekitd. send ( skreq, self . queue) { [ weak self] result in
998
- guard let self = self else { return }
994
+ final class FoldingRangeFinder : SyntaxVisitor {
995
+ private let snapshot : DocumentSnapshot
996
+ /// Some ranges might occur multiple times.
997
+ /// E.g. for `print("hi")`, `"hi"` is both the range of all call arguments and the range the first argument in the call.
998
+ /// It doesn't make sense to report them multiple times, so use a `Set` here.
999
+ private var ranges : Set < FoldingRange >
1000
+ /// The client-imposed limit on the number of folding ranges it would
1001
+ /// prefer to recieve from the LSP server. If the value is `nil`, there
1002
+ /// is no preset limit.
1003
+ private var rangeLimit : Int ?
1004
+ /// If `true`, the client is only capable of folding entire lines. If
1005
+ /// `false` the client can handle folding ranges.
1006
+ private var lineFoldingOnly : Bool
1007
+
1008
+ init ( snapshot: DocumentSnapshot , rangeLimit: Int ? , lineFoldingOnly: Bool ) {
1009
+ self . snapshot = snapshot
1010
+ self . ranges = [ ]
1011
+ self . rangeLimit = rangeLimit
1012
+ self . lineFoldingOnly = lineFoldingOnly
1013
+ super. init ( viewMode: . sourceAccurate)
1014
+ }
999
1015
1000
- defer {
1001
- let closeHelperReq = SKDRequestDictionary ( sourcekitd: self . sourcekitd)
1002
- closeHelperReq [ keys. request] = self . requests. editor_close
1003
- closeHelperReq [ keys. name] = helperDocumentName
1004
- _ = self . sourcekitd. send ( closeHelperReq, . global( qos: . utility) , reply: { _ in } )
1016
+ override func visit( _ node: TokenSyntax ) -> SyntaxVisitorContinueKind {
1017
+ // Index comments, so we need to see at least '/*', or '//'.
1018
+ if node. leadingTriviaLength. utf8Length > 2 {
1019
+ self . addTrivia ( from: node, node. leadingTrivia)
1020
+ }
1021
+
1022
+ if node. trailingTriviaLength. utf8Length > 2 {
1023
+ self . addTrivia ( from: node, node. trailingTrivia)
1024
+ }
1025
+
1026
+ return . visitChildren
1005
1027
}
1006
1028
1007
- guard let dict = result. success else {
1008
- req. reply ( . failure( ResponseError ( result. failure!) ) )
1009
- return
1029
+ private func addTrivia( from node: TokenSyntax , _ trivia: Trivia ) {
1030
+ var start = node. position. utf8Offset
1031
+ var lineCommentStart : Int ? = nil
1032
+ func flushLineComment( _ offset: Int = 0 ) {
1033
+ if let lineCommentStart = lineCommentStart {
1034
+ _ = self . addFoldingRange (
1035
+ start: lineCommentStart,
1036
+ end: start + offset,
1037
+ kind: . comment)
1038
+ }
1039
+ lineCommentStart = nil
1040
+ }
1041
+
1042
+ for piece in node. leadingTrivia {
1043
+ defer { start += piece. sourceLength. utf8Length }
1044
+ switch piece {
1045
+ case . blockComment( _) :
1046
+ flushLineComment ( )
1047
+ _ = self . addFoldingRange (
1048
+ start: start,
1049
+ end: start + piece. sourceLength. utf8Length,
1050
+ kind: . comment)
1051
+ case . docBlockComment( _) :
1052
+ flushLineComment ( )
1053
+ _ = self . addFoldingRange (
1054
+ start: start,
1055
+ end: start + piece. sourceLength. utf8Length,
1056
+ kind: . comment)
1057
+ case . lineComment( _) , . docLineComment( _) :
1058
+ if lineCommentStart == nil {
1059
+ lineCommentStart = start
1060
+ }
1061
+ case . newlines( 1 ) , . carriageReturns( 1 ) , . spaces( _) , . tabs( _) :
1062
+ if lineCommentStart != nil {
1063
+ continue
1064
+ } else {
1065
+ flushLineComment ( )
1066
+ }
1067
+ default :
1068
+ flushLineComment ( )
1069
+ continue
1070
+ }
1071
+ }
1072
+
1073
+ flushLineComment ( )
1010
1074
}
1011
1075
1012
- guard let syntaxMap: SKDResponseArray = dict [ self . keys. syntaxmap] ,
1013
- let substructure: SKDResponseArray = dict [ self . keys. substructure] else {
1014
- return req. reply ( [ ] )
1076
+ override func visit( _ node: CodeBlockSyntax ) -> SyntaxVisitorContinueKind {
1077
+ return self . addFoldingRange (
1078
+ start: node. statements. position. utf8Offset,
1079
+ end: node. rightBrace. positionAfterSkippingLeadingTrivia. utf8Offset)
1015
1080
}
1016
1081
1017
- /// Some ranges might occur multiple times.
1018
- /// E.g. for `print("hi")`, `"hi"` is both the range of all call arguments and the range the first argument in the call.
1019
- /// It doesn't make sense to report them multiple times, so use a `Set` here.
1020
- var ranges : Set < FoldingRange > = [ ]
1082
+ override func visit( _ node: MemberDeclBlockSyntax ) -> SyntaxVisitorContinueKind {
1083
+ return self . addFoldingRange (
1084
+ start: node. members. position. utf8Offset,
1085
+ end: node. rightBrace. positionAfterSkippingLeadingTrivia. utf8Offset)
1086
+ }
1021
1087
1022
- var hasReachedLimit : Bool {
1023
- let capabilities = self . clientCapabilities. textDocument? . foldingRange
1024
- guard let rangeLimit = capabilities? . rangeLimit else {
1025
- return false
1026
- }
1027
- return ranges. count >= rangeLimit
1088
+ override func visit( _ node: ClosureExprSyntax ) -> SyntaxVisitorContinueKind {
1089
+ return self . addFoldingRange (
1090
+ start: node. statements. position. utf8Offset,
1091
+ end: node. rightBrace. positionAfterSkippingLeadingTrivia. utf8Offset)
1028
1092
}
1029
1093
1030
- // If the limit is less than one, do nothing.
1031
- guard hasReachedLimit == false else {
1032
- req . reply ( [ ] )
1033
- return
1094
+ override func visit ( _ node : AccessorBlockSyntax ) -> SyntaxVisitorContinueKind {
1095
+ return self . addFoldingRange (
1096
+ start : node . accessors . position . utf8Offset ,
1097
+ end : node . rightBrace . positionAfterSkippingLeadingTrivia . utf8Offset )
1034
1098
}
1035
1099
1036
- // Merge successive comments into one big comment by adding their lengths.
1037
- var currentComment : ( offset: Int , length: Int ) ? = nil
1100
+ override func visit( _ node: SwitchStmtSyntax ) -> SyntaxVisitorContinueKind {
1101
+ return self . addFoldingRange (
1102
+ start: node. cases. position. utf8Offset,
1103
+ end: node. rightBrace. positionAfterSkippingLeadingTrivia. utf8Offset)
1104
+ }
1038
1105
1039
- syntaxMap. forEach { _, value in
1040
- if let kind: sourcekitd_uid_t = value [ self . keys. kind] ,
1041
- kind. isCommentKind ( self . values) ,
1042
- let offset: Int = value [ self . keys. offset] ,
1043
- let length: Int = value [ self . keys. length]
1044
- {
1045
- if let comment = currentComment, comment. offset + comment. length == offset {
1046
- currentComment!. length += length
1047
- return true
1048
- }
1049
- if let comment = currentComment {
1050
- self . addFoldingRange ( offset: comment. offset, length: comment. length, kind: . comment, in: snapshot, toSet: & ranges)
1051
- }
1052
- currentComment = ( offset: offset, length: length)
1053
- }
1054
- return hasReachedLimit == false
1106
+ override func visit( _ node: FunctionCallExprSyntax ) -> SyntaxVisitorContinueKind {
1107
+ return self . addFoldingRange (
1108
+ start: node. argumentList. position. utf8Offset,
1109
+ end: node. argumentList. endPosition. utf8Offset)
1055
1110
}
1056
1111
1057
- // Add the last stored comment.
1058
- if let comment = currentComment , hasReachedLimit == false {
1059
- self . addFoldingRange ( offset : comment . offset , length : comment . length , kind : . comment , in : snapshot , toSet : & ranges )
1060
- currentComment = nil
1112
+ override func visit ( _ node : SubscriptExprSyntax ) -> SyntaxVisitorContinueKind {
1113
+ return self . addFoldingRange (
1114
+ start : node . argumentList . position . utf8Offset ,
1115
+ end : node . argumentList . endPosition . utf8Offset )
1061
1116
}
1062
1117
1063
- var structureStack : [ SKDResponseArray ] = [ substructure]
1064
- while !hasReachedLimit, let substructure = structureStack. popLast ( ) {
1065
- substructure. forEach { _, value in
1066
- if let offset: Int = value [ self . keys. bodyoffset] ,
1067
- let length: Int = value [ self . keys. bodylength] ,
1068
- length > 0
1069
- {
1070
- self . addFoldingRange ( offset: offset, length: length, in: snapshot, toSet: & ranges)
1071
- if hasReachedLimit {
1072
- return false
1073
- }
1074
- }
1075
- if let substructure: SKDResponseArray = value [ self . keys. substructure] {
1076
- structureStack. append ( substructure)
1118
+ __consuming func finalize( ) -> Set < FoldingRange > {
1119
+ return self . ranges
1120
+ }
1121
+
1122
+ private func addFoldingRange( start: Int , end: Int , kind: FoldingRangeKind ? = nil ) -> SyntaxVisitorContinueKind {
1123
+ if let limit = self . rangeLimit, self . ranges. count >= limit {
1124
+ return . skipChildren
1125
+ }
1126
+
1127
+ guard let start: Position = snapshot. positionOf ( utf8Offset: start) ,
1128
+ let end: Position = snapshot. positionOf ( utf8Offset: end) else {
1129
+ log ( " folding range failed to retrieve position of \( snapshot. document. uri) : \( start) - \( end) " , level: . warning)
1130
+ return . visitChildren
1131
+ }
1132
+ let range : FoldingRange
1133
+ if lineFoldingOnly {
1134
+ // Since the client cannot fold less than a single line, if the
1135
+ // fold would span 1 line there's no point in reporting it.
1136
+ guard end. line > start. line else {
1137
+ return . visitChildren
1077
1138
}
1078
- return true
1139
+
1140
+ // If the client only supports folding full lines, don't report
1141
+ // the end of the range since there's nothing they could do with it.
1142
+ range = FoldingRange ( startLine: start. line,
1143
+ startUTF16Index: nil ,
1144
+ endLine: end. line,
1145
+ endUTF16Index: nil ,
1146
+ kind: kind)
1147
+ } else {
1148
+ range = FoldingRange ( startLine: start. line,
1149
+ startUTF16Index: start. utf16index,
1150
+ endLine: end. line,
1151
+ endUTF16Index: end. utf16index,
1152
+ kind: kind)
1079
1153
}
1154
+ ranges. insert ( range)
1155
+ return . visitChildren
1080
1156
}
1081
-
1082
- req. reply ( ranges. sorted ( ) )
1083
1157
}
1084
1158
1085
- // FIXME: cancellation
1086
- _ = handle
1087
- }
1088
- }
1089
-
1090
- func addFoldingRange( offset: Int , length: Int , kind: FoldingRangeKind ? = nil , in snapshot: DocumentSnapshot , toSet ranges: inout Set < FoldingRange > ) {
1091
- guard let start: Position = snapshot. positionOf ( utf8Offset: offset) ,
1092
- let end: Position = snapshot. positionOf ( utf8Offset: offset + length) else {
1093
- log ( " folding range failed to retrieve position of \( snapshot. document. uri) : \( offset) - \( offset + length) " , level: . warning)
1094
- return
1095
- }
1096
- let capabilities = clientCapabilities. textDocument? . foldingRange
1097
- let range : FoldingRange
1098
- // If the client only supports folding full lines, ignore the end character's line.
1099
- if capabilities? . lineFoldingOnly == true {
1100
- let lastLineToFold = end. line - 1
1101
- if lastLineToFold <= start. line {
1159
+ let capabilities = self . clientCapabilities. textDocument? . foldingRange
1160
+ // If the limit is less than one, do nothing.
1161
+ if let limit = capabilities? . rangeLimit, limit <= 0 {
1162
+ req. reply ( [ ] )
1102
1163
return
1103
- } else {
1104
- range = FoldingRange ( startLine: start. line,
1105
- startUTF16Index: nil ,
1106
- endLine: lastLineToFold,
1107
- endUTF16Index: nil ,
1108
- kind: kind)
1109
1164
}
1110
- } else {
1111
- range = FoldingRange ( startLine: start. line,
1112
- startUTF16Index: start. utf16index,
1113
- endLine: end. line,
1114
- endUTF16Index: end. utf16index,
1115
- kind: kind)
1165
+
1166
+ let rangeFinder = FoldingRangeFinder (
1167
+ snapshot: snapshot,
1168
+ rangeLimit: capabilities? . rangeLimit,
1169
+ lineFoldingOnly: capabilities? . lineFoldingOnly ?? false )
1170
+ rangeFinder. walk ( sourceFile)
1171
+ let ranges = rangeFinder. finalize ( )
1172
+
1173
+ req. reply ( ranges. sorted ( ) )
1116
1174
}
1117
- ranges. insert ( range)
1118
1175
}
1119
1176
1120
1177
public func codeAction( _ req: Request < CodeActionRequest > ) {
0 commit comments