|
10 | 10 | //
|
11 | 11 | //===----------------------------------------------------------------------===//
|
12 | 12 |
|
| 13 | +import Foundation |
13 | 14 | import LSPLogging
|
14 | 15 | import LanguageServerProtocol
|
15 | 16 | import SwiftSyntax
|
@@ -109,6 +110,51 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
|
109 | 110 | )
|
110 | 111 | }
|
111 | 112 |
|
| 113 | + private func visit(_ trivia: Trivia, position: AbsolutePosition) { |
| 114 | + var position = position |
| 115 | + for piece in trivia.pieces { |
| 116 | + defer { |
| 117 | + position = position.advanced(by: piece.sourceLength.utf8Length) |
| 118 | + } |
| 119 | + switch piece { |
| 120 | + case .lineComment(let commentText), .blockComment(let commentText): |
| 121 | + let trimmedComment = commentText.trimmingPrefix(while: { $0 == "/" || $0 == "*" || $0 == " " }) |
| 122 | + if trimmedComment.starts(with: "MARK: ") { |
| 123 | + let markText = String(trimmedComment[trimmedComment.index(trimmedComment.startIndex, offsetBy: 6)...]) |
| 124 | + guard let rangeLowerBound = snapshot.position(of: position), |
| 125 | + let rangeUpperBound = snapshot.position(of: position.advanced(by: piece.sourceLength.utf8Length)) |
| 126 | + else { |
| 127 | + break |
| 128 | + } |
| 129 | + let documentSymbolName: String |
| 130 | + switch piece { |
| 131 | + case .lineComment: |
| 132 | + documentSymbolName = markText.trimmingCharacters(in: CharacterSet(["/", "*"]).union(.whitespaces)) |
| 133 | + case .blockComment: documentSymbolName = markText.trimmingCharacters(in: .whitespaces) |
| 134 | + default: preconditionFailure("Trivia piece kind not covered in the outer case") |
| 135 | + } |
| 136 | + result.append( |
| 137 | + DocumentSymbol( |
| 138 | + name: documentSymbolName, |
| 139 | + kind: .namespace, |
| 140 | + range: rangeLowerBound..<rangeUpperBound, |
| 141 | + selectionRange: rangeLowerBound..<rangeUpperBound, |
| 142 | + children: nil |
| 143 | + ) |
| 144 | + ) |
| 145 | + } |
| 146 | + default: |
| 147 | + break |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { |
| 153 | + self.visit(node.leadingTrivia, position: node.position) |
| 154 | + self.visit(node.trailingTrivia, position: node.endPositionBeforeTrailingTrivia) |
| 155 | + return .skipChildren |
| 156 | + } |
| 157 | + |
112 | 158 | override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind {
|
113 | 159 | let rangeEnd =
|
114 | 160 | if let parameterClause = node.parameterClause {
|
@@ -253,3 +299,15 @@ fileprivate extension TokenKind {
|
253 | 299 | }
|
254 | 300 | }
|
255 | 301 | }
|
| 302 | + |
| 303 | +fileprivate extension String { |
| 304 | + /// Compatibility function of the `trimmingPrefix` function in the stdlib. We should be able to remove this once we |
| 305 | + /// only support macOS 13+ |
| 306 | + func trimmingPrefix(while condition: (Character) -> Bool) -> Substring { |
| 307 | + var result = self[...] |
| 308 | + while let first = result.first, condition(first) { |
| 309 | + result = result[result.index(after: result.startIndex)...] |
| 310 | + } |
| 311 | + return result |
| 312 | + } |
| 313 | +} |
0 commit comments