|
10 | 10 | //
|
11 | 11 | //===----------------------------------------------------------------------===//
|
12 | 12 |
|
| 13 | +import Foundation |
13 | 14 | import LSPLogging
|
14 | 15 | import LanguageServerProtocol
|
15 | 16 | import SwiftSyntax
|
@@ -109,6 +110,45 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
|
109 | 110 | )
|
110 | 111 | }
|
111 | 112 |
|
| 113 | + private func visit(_ trivia: Trivia, position: AbsolutePosition) { |
| 114 | + let markPrefix = "MARK: " |
| 115 | + var position = position |
| 116 | + for piece in trivia.pieces { |
| 117 | + defer { |
| 118 | + position = position.advanced(by: piece.sourceLength.utf8Length) |
| 119 | + } |
| 120 | + switch piece { |
| 121 | + case .lineComment(let commentText), .blockComment(let commentText): |
| 122 | + let trimmedComment = commentText.trimmingCharacters(in: CharacterSet(["/", "*"]).union(.whitespaces)) |
| 123 | + if trimmedComment.starts(with: markPrefix) { |
| 124 | + let markText = trimmedComment.dropFirst(markPrefix.count) |
| 125 | + guard let rangeLowerBound = snapshot.position(of: position), |
| 126 | + let rangeUpperBound = snapshot.position(of: position.advanced(by: piece.sourceLength.utf8Length)) |
| 127 | + else { |
| 128 | + break |
| 129 | + } |
| 130 | + result.append( |
| 131 | + DocumentSymbol( |
| 132 | + name: String(markText), |
| 133 | + kind: .namespace, |
| 134 | + range: rangeLowerBound..<rangeUpperBound, |
| 135 | + selectionRange: rangeLowerBound..<rangeUpperBound, |
| 136 | + children: nil |
| 137 | + ) |
| 138 | + ) |
| 139 | + } |
| 140 | + default: |
| 141 | + break |
| 142 | + } |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { |
| 147 | + self.visit(node.leadingTrivia, position: node.position) |
| 148 | + self.visit(node.trailingTrivia, position: node.endPositionBeforeTrailingTrivia) |
| 149 | + return .skipChildren |
| 150 | + } |
| 151 | + |
112 | 152 | override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind {
|
113 | 153 | let rangeEnd =
|
114 | 154 | if let parameterClause = node.parameterClause {
|
@@ -180,7 +220,9 @@ fileprivate final class DocumentSymbolsFinder: SyntaxAnyVisitor {
|
180 | 220 | // If there is only one pattern binding within the variable decl, consider the entire variable decl as the
|
181 | 221 | // referenced range. If there are multiple, consider each pattern binding separately since the `var` keyword doesn't
|
182 | 222 | // belong to any pattern binding in particular.
|
183 |
| - guard let variableDecl = node.parent?.parent?.as(VariableDeclSyntax.self) else { |
| 223 | + guard let variableDecl = node.parent?.parent?.as(VariableDeclSyntax.self), |
| 224 | + variableDecl.isMemberOrTopLevelDeclaration |
| 225 | + else { |
184 | 226 | return .visitChildren
|
185 | 227 | }
|
186 | 228 | let rangeNode: Syntax = variableDecl.bindings.count == 1 ? Syntax(variableDecl) : Syntax(node)
|
@@ -228,6 +270,19 @@ fileprivate extension SyntaxProtocol {
|
228 | 270 | var rangeWithoutTrivia: Range<AbsolutePosition> {
|
229 | 271 | return positionAfterSkippingLeadingTrivia..<endPositionBeforeTrailingTrivia
|
230 | 272 | }
|
| 273 | + |
| 274 | + /// Whether this is a top-level constant or a member of a type, ie. if this is not a local variable. |
| 275 | + var isMemberOrTopLevelDeclaration: Bool { |
| 276 | + if self.parent?.is(MemberBlockItemSyntax.self) ?? false { |
| 277 | + return true |
| 278 | + } |
| 279 | + if let codeBlockItem = self.parent?.as(CodeBlockItemSyntax.self), |
| 280 | + codeBlockItem.parent?.parent?.is(SourceFileSyntax.self) ?? false |
| 281 | + { |
| 282 | + return true |
| 283 | + } |
| 284 | + return false |
| 285 | + } |
231 | 286 | }
|
232 | 287 |
|
233 | 288 | fileprivate extension TokenKind {
|
|
0 commit comments