From 29f4e1ed4c20f399ee5f6c85b5542deb3ac41727 Mon Sep 17 00:00:00 2001 From: Lokesh T R Date: Tue, 23 Dec 2025 03:02:05 +0530 Subject: [PATCH 1/4] Implement Document Highlight for all return / throws (function/closure/accessor/init/deinit) and break / continue (for/while/repeat...while) --- Package.swift | 4 +- .../SwiftLanguageService.swift | 70 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 657271d2f..110b02aeb 100644 --- a/Package.swift +++ b/Package.swift @@ -508,6 +508,7 @@ var targets: [Target] = [ "SwiftBasicFormat", "SwiftDiagnostics", "SwiftIDEUtils", + "SwiftLexicalLookup", "SwiftParser", "SwiftParserDiagnostics", "SwiftRefactor", @@ -785,7 +786,8 @@ var dependencies: [Package.Dependency] { .package(url: "https://github.com/swiftlang/swift-tools-protocols.git", exact: "0.0.9"), .package(url: "https://github.com/swiftlang/swift-tools-support-core.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.1"), - .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: relatedDependenciesBranch), + // .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: relatedDependenciesBranch), + .package(path: "../swift-syntax"), .package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0"), // Not a build dependency. Used so the "Format Source Code" command plugin can be used to format sourcekit-lsp .package(url: "https://github.com/swiftlang/swift-format.git", branch: relatedDependenciesBranch), diff --git a/Sources/SwiftLanguageService/SwiftLanguageService.swift b/Sources/SwiftLanguageService/SwiftLanguageService.swift index 639a5a64a..b5f16e120 100644 --- a/Sources/SwiftLanguageService/SwiftLanguageService.swift +++ b/Sources/SwiftLanguageService/SwiftLanguageService.swift @@ -31,6 +31,8 @@ package import SwiftSyntax package import ToolchainRegistry @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions +@_spi(Experimental) import SwiftLexicalLookup + #if os(Windows) import WinSDK #endif @@ -875,6 +877,13 @@ extension SwiftLanguageService { package func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? { let snapshot = try await self.latestSnapshot(for: req.textDocument.uri) + + if let highlights = try await documentSymbolHighlightHelper( + at: req.position, + in: snapshot + ) { + return highlights + } let relatedIdentifiers = try await self.relatedIdentifiers( at: req.position, @@ -889,6 +898,67 @@ extension SwiftLanguageService { } } + private func documentSymbolHighlightHelper( + at position: Position, + in snapshot: DocumentSnapshot + ) async throws -> [DocumentHighlight]? { + let syntaxTree = await syntaxTreeManager.syntaxTree(for: snapshot) + + guard let tokenSyntax = syntaxTree.token(at: snapshot.absolutePosition(of: position)) else { + return nil + } + + guard let targetStructure = Syntax(tokenSyntax).parent!.lookupControlStructure() else { + return nil + } + class ControlFlowHighlighter: SyntaxVisitor { + var highlights: [DocumentHighlight] = [] + let targetStructure: Syntax + let snapshot: DocumentSnapshot + + init(targetStructure: Syntax, snapshot: DocumentSnapshot) { + self.targetStructure = targetStructure + self.snapshot = snapshot + super.init(viewMode: .sourceAccurate) + } + + private func addHighlightIfMatches(_ node: some SyntaxProtocol) { + if node.lookupControlStructure() == targetStructure { + highlights.append( + DocumentHighlight( + range: snapshot.absolutePositionRange(of: node.positionAfterSkippingLeadingTrivia.. SyntaxVisitorContinueKind { + addHighlightIfMatches(node) + return .skipChildren + } + + override func visit(_ node: ContinueStmtSyntax) -> SyntaxVisitorContinueKind { + addHighlightIfMatches(node) + return .skipChildren + } + + override func visit(_ node: ReturnStmtSyntax) -> SyntaxVisitorContinueKind { + addHighlightIfMatches(node) + return .skipChildren + } + + override func visit(_ node: ThrowStmtSyntax) -> SyntaxVisitorContinueKind { + addHighlightIfMatches(node) + return .skipChildren + } + } + + let highlighter = ControlFlowHighlighter(targetStructure: targetStructure, snapshot: snapshot) + highlighter.walk(targetStructure) + return highlighter.highlights.isEmpty ? nil : highlighter.highlights + } + package func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse? { if (try? ReferenceDocumentURL(from: req.textDocument.uri)) != nil { // Do not show code actions in reference documents From 912eb60c96024f33569387123b09b48ee320e826 Mon Sep 17 00:00:00 2001 From: Lokesh T R Date: Wed, 31 Dec 2025 09:37:26 +0530 Subject: [PATCH 2/4] Address Comments: Avoid Parent Unwrap --- Sources/SwiftLanguageService/SwiftLanguageService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftLanguageService/SwiftLanguageService.swift b/Sources/SwiftLanguageService/SwiftLanguageService.swift index b5f16e120..8d2cefde2 100644 --- a/Sources/SwiftLanguageService/SwiftLanguageService.swift +++ b/Sources/SwiftLanguageService/SwiftLanguageService.swift @@ -908,7 +908,7 @@ extension SwiftLanguageService { return nil } - guard let targetStructure = Syntax(tokenSyntax).parent!.lookupControlStructure() else { + guard let parent = Syntax(tokenSyntax).parent, let targetStructure = parent.lookupControlStructure() else { return nil } class ControlFlowHighlighter: SyntaxVisitor { From aae35435af4ee7693353483d36de60f03b112b17 Mon Sep 17 00:00:00 2001 From: Lokesh T R Date: Wed, 31 Dec 2025 09:41:34 +0530 Subject: [PATCH 3/4] Address Comment: Descriptive name for the control flow keyword highlighting function --- Sources/SwiftLanguageService/SwiftLanguageService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftLanguageService/SwiftLanguageService.swift b/Sources/SwiftLanguageService/SwiftLanguageService.swift index 8d2cefde2..7c0aee103 100644 --- a/Sources/SwiftLanguageService/SwiftLanguageService.swift +++ b/Sources/SwiftLanguageService/SwiftLanguageService.swift @@ -878,7 +878,7 @@ extension SwiftLanguageService { package func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? { let snapshot = try await self.latestSnapshot(for: req.textDocument.uri) - if let highlights = try await documentSymbolHighlightHelper( + if let highlights = try await controlFlowExitKeywordHighlight( at: req.position, in: snapshot ) { @@ -898,7 +898,7 @@ extension SwiftLanguageService { } } - private func documentSymbolHighlightHelper( + private func controlFlowExitKeywordHighlight( at position: Position, in snapshot: DocumentSnapshot ) async throws -> [DocumentHighlight]? { From 11124e9dc89236fc43016da092a53e793ca492d1 Mon Sep 17 00:00:00 2001 From: Lokesh T R Date: Wed, 31 Dec 2025 10:27:27 +0530 Subject: [PATCH 4/4] Address Comments: Highlight only the control flow keyword to make it look nicer for expressions spanning multiple lines --- Sources/SwiftLanguageService/SwiftLanguageService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftLanguageService/SwiftLanguageService.swift b/Sources/SwiftLanguageService/SwiftLanguageService.swift index 7c0aee103..0a6e49684 100644 --- a/Sources/SwiftLanguageService/SwiftLanguageService.swift +++ b/Sources/SwiftLanguageService/SwiftLanguageService.swift @@ -926,7 +926,7 @@ extension SwiftLanguageService { if node.lookupControlStructure() == targetStructure { highlights.append( DocumentHighlight( - range: snapshot.absolutePositionRange(of: node.positionAfterSkippingLeadingTrivia..