//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// @_spi(SourceKitLSP) package import LanguageServerProtocol @_spi(SourceKitLSP) import SKLogging import SourceKitLSP import SwiftOperators import SwiftSyntax extension SwiftLanguageService { package func selectionRange(_ req: SelectionRangeRequest) async throws -> [SelectionRange] { let snapshot = try self.documentManager.latestSnapshot(req.textDocument.uri) let sourceFile = await syntaxTreeManager.syntaxTree(for: snapshot) try Task.checkCancellation() return req.positions.map { position in let absolutePosition = snapshot.absolutePosition(of: position) guard let (token, newPosition) = findIntuitiveToken(in: sourceFile, at: absolutePosition) else { return SelectionRange(range: position.. (TokenSyntax, AbsolutePosition)? { guard let currentToken = sourceFile.token(at: position) else { if (sourceFile.endPositionBeforeTrailingTrivia...sourceFile.endPosition).contains(position), // The last token is EOF, so we use the token just before EOF let newToken = sourceFile.lastToken(viewMode: .sourceAccurate)?.previousToken(viewMode: .sourceAccurate) { return (newToken, newToken.endPosition.advanced(by: -1)) } return nil } let boundaryTokens: [TokenKind] = [ .leftParen, .rightParen, .leftBrace, .rightBrace, .leftSquare, .rightSquare, .leftAngle, .rightAngle, .comma, .period, .semicolon, .colon, ] if position == currentToken.position && boundaryTokens.contains(currentToken.tokenKind) { // The cursor is at the start of a boundary token (e.g. `test(a: 3, b: 2|)`) // here the user most likely wants to select the `2` and then `b: 2` instead of // selecting the entire function call, so we use the previous token let newToken = currentToken.previousToken(viewMode: .sourceAccurate) ?? currentToken if newToken.endPositionBeforeTrailingTrivia == position { return (newToken, newToken.endPosition.advanced(by: -1)) } } return (currentToken, position) } private func computeSelectionRangeFor( position: AbsolutePosition, snapshot: DocumentSnapshot, node: Syntax ) -> SelectionRange? { var ranges: [Range] = [] var current = node while true { let rangesForNode = calculateRangesFor(node: current, snapshot: snapshot, position: position) for range in rangesForNode { if ranges.last == range { // Some AST nodes have the exact same range, we just skip creating ranges for them continue } ranges.append(range) } guard let parent = current.parent else { break } current = parent } var selectionRange: SelectionRange? = nil for range in ranges.reversed() { let start = snapshot.position(of: range.lowerBound) let end = snapshot.position(of: range.upperBound) selectionRange = SelectionRange(range: start.. [Range] { if let stringSegment = node.as(StringSegmentSyntax.self) { return calculateSelectionRangesForStringSegment(stringSegment: stringSegment, position: position) } if node.isProtocol((any DeclGroupSyntax).self) || node.is(TypeAliasDeclSyntax.self) || node.is(FunctionDeclSyntax.self) { let name = Syntax(node.asProtocol((any NamedDeclSyntax).self)?.name) let type = Syntax(node.as(ExtensionDeclSyntax.self)?.extendedType) let genericParameterClause = node.asProtocol((any WithGenericParametersSyntax).self)?.genericParameterClause if let nameOrType = name ?? type { return calculateRangesForTypeOrFunctionDeclaration( declaration: node, position: position, nameOrType: nameOrType, genericParameters: genericParameterClause ) } } if let provider = node.asProtocol((any SyntaxProtocol).self) as? (any SelectionRangeProvider) { return provider.calculateSelectionRanges(position: position) } return [] } private func calculateSelectionRangesForStringSegment( stringSegment: StringSegmentSyntax, position: AbsolutePosition ) -> [Range] { // For string segments we first want to select just the word under the cursor. // To determine words we use a simple heuristic: expand the selection until we hit any non-letter character. let offsetInString = position.utf8Offset - stringSegment.positionAfterSkippingLeadingTrivia.utf8Offset let text = stringSegment.content.text let index = text.utf8.index(text.startIndex, offsetBy: offsetInString) if !text[index].isLetter { return [] } let start = text[.. [Range] { // If we started the selection in either the name (or type in the case of extensions) // or the generic parameter clause of a declaration we want to have a selection range // for just the name (type) and generic parameter clause. // // Example: given `struct Test<|T> {}` we want to have a selection range for `Test` // // As the name (type) and generic parameter clause are siblings in the declaration nodes // we have to special-case them. var ranges: [Range] = [] if let genericParameters = genericParameters, nameOrType.range.contains(position) || genericParameters.range.contains(position) { let start = nameOrType.positionAfterSkippingLeadingTrivia let end = genericParameters.endPositionBeforeTrailingTrivia ranges.append(start.. [Range] } extension FunctionCallExprSyntax: SelectionRangeProvider { func calculateSelectionRanges(position: AbsolutePosition) -> [Range] { if let memberAccess = self.calledExpression.as(MemberAccessExprSyntax.self), !(self.parent?.is(ExpressionPatternSyntax.self) ?? false), (memberAccess.declName.position.. 0 } // .map { $0 * 2 } // .reduce(0,| +) // // When starting a selection from | we want to have a selection for `reduce(0, +)` in addition to selecting // the entire function call (starting from `numbers`) return [ memberAccess.declName.positionAfterSkippingLeadingTrivia.. [Range] { // For subscript calls we want to have a selection range for the entire subscript operator // including the `[]` // Example: given `matrix[2, |3]` we want to be able to select `[2, 3]` if self.arguments.range.contains(position) { let start = self.leftSquare.positionAfterSkippingLeadingTrivia let end = self.rightSquare.endPositionBeforeTrailingTrivia return [start.. [Range] { // For labeled expressions we want to be able to select just the label and expression without the comma let start = self.positionAfterSkippingLeadingTrivia let end = self.expression.endPositionBeforeTrailingTrivia return [start.. [Range] { // Don't include the trailing comma in the selection, except if the parameter is the only one if let parameterList = self.parent?.as(GenericParameterListSyntax.self), parameterList.count == 1 { return [self.trimmedRange] } let end = self.trailingComma?.position ?? self.endPositionBeforeTrailingTrivia return [self.positionAfterSkippingLeadingTrivia.. [Range] { // Function parameters have two special cases: // - If the cursor is in the type and the type includes an ellipsis we want to have a range for the type and ellipsis // - If the parameter has two names we want to have a range for selecting both names let start = self.positionAfterSkippingLeadingTrivia let end = self.trailingComma?.position ?? self.endPositionBeforeTrailingTrivia let rangeWithoutComma = start..] = [] if let secondName = self.secondName { // If the parameter has two names, add an additional range for selecting both names let range = self.firstName.positionAfterSkippingLeadingTrivia.. [Range] { var ranges: [Range] = [] if let signature = self.signature, signature.range.contains(position) { let start = signature.positionAfterSkippingLeadingTrivia let end = self.statements.endPositionBeforeTrailingTrivia ranges.append(start.. [Range] { // For enum case parameters we also add a range for selecting the names of the parameter, similar to function parameters // This implementation is really similar to the one for FunctionParameterSyntax, // except that we don't have to deal with ellipses and have to deal with unlabeled parameters let start = self.positionAfterSkippingLeadingTrivia let end = self.trailingComma?.position ?? self.endPositionBeforeTrailingTrivia let rangeWithoutComma = start..] = [] if let firstName = self.firstName, let secondName = self.secondName { // The parameter has two names, add a selection range for selecting both names let range = firstName.positionAfterSkippingLeadingTrivia.. [Range] { // Expression lists are more complex to deal with as we first have to convert the expression list into the // corresponding tree. We can then find the node the cursor is on in the tree and walk the tree up to its root guard let sequenceExpression = self.parent?.as(SequenceExprSyntax.self) else { return [self.trimmedRange] } let table = OperatorTable.standardOperators let foldedTree = orLog("Folding ExprListSyntax") { try table.foldSingle(sequenceExpression) } guard let foldedTree = foldedTree else { return [] } let foldedTreeOffset = SourceLength(utf8Length: sequenceExpression.position.utf8Offset) let offsetInTree = position - foldedTreeOffset guard var operandNode = foldedTree.token(at: offsetInTree)?.parent else { return [] } // Walk up from the token to the operand node // This is needed to avoid processing everything below the operand node two times, as everything below has already // been processed by the normal logic before hitting the ExprListSyntax while let parent = operandNode.parent { if parent.is(InfixOperatorExprSyntax.self) { break } operandNode = parent } var ranges: [Range] = [] for node in sequence(first: operandNode, next: \.parent) { let startPosition = node.positionAfterSkippingLeadingTrivia + foldedTreeOffset let endPosition = node.endPositionBeforeTrailingTrivia + foldedTreeOffset ranges.append(startPosition.. [Range] { // For pattern bindings we special-case depending on whether we have a single pattern binding or multiple guard let patternBindingList = self.parent?.as(PatternBindingListSyntax.self) else { return [] } if patternBindingList.children(viewMode: .sourceAccurate).count > 1 { // Special case for pattern bindings like this: `let x = 1, y = 2, z = 3` // Here we want to be able to select only `y = 2` let start = self.positionAfterSkippingLeadingTrivia let end = self.trailingComma?.position ?? self.endPositionBeforeTrailingTrivia return [start.. [Range] { var ranges: [Range] = [] if let elseKeyword = self.elseKeyword, let elseBody = self.elseBody { // When inside the else block add a range for selecting `else {...}` let range = elseKeyword.positionAfterSkippingLeadingTrivia.. [Range] { // For statements get an extra range for selecting from the pattern until the sequence, // i.e. selecting `i in 1...3` in `for i in 1...3 {}` // As the for statement can have a lot of immediate children, more special cases can be added here in the future, // for example for the keywords before the pattern var ranges: [Range] = [] if (self.pattern.position.. [Range] { let start = self.positionAfterSkippingLeadingTrivia let end = self.trailingComma?.position ?? self.endPositionBeforeTrailingTrivia return [start.. [Range] { if self.parent?.is(FunctionCallExprSyntax.self) ?? false { // If the member access is part of a function call, we don't return any range as this case is handled in the // FunctionCallExprSyntax extension return [] } return [self.trimmedRange] } } extension AvailabilityArgumentSyntax: SelectionRangeProvider { func calculateSelectionRanges(position: AbsolutePosition) -> [Range] { if let trailingComma = self.trailingComma { let start = self.positionAfterSkippingLeadingTrivia let end = trailingComma.positionAfterSkippingLeadingTrivia return [start.. [Range] { switch self.tokenKind { case .identifier where self.parent?.keyPathInParent == \AttributeSyntax.attributeName: // For attributes we don't want to create a range for just the attribute name but rather always include the `@` return [] case .identifier where self.keyPathInParent == \MacroExpansionExprSyntax.macroName: // For macro expansions we don't want to create a range for just the macro name but rather always include the `#` return [] case .identifier where self.keyPathInParent == \GenericParameterSyntax.name: // For generic parameters we want to handle the identifier in the `GenericParameter` node as we may have to // include or exclude the trailing comma return [] case .keyword(let keyword) where keyword == .as: // The `as` keyword should always be handled by the `UnresolvedAsExpr` node as it also includes the `!` or `?` return [] case .binaryOperator, .dollarIdentifier, .floatLiteral, .identifier, .integerLiteral, .keyword: return [self.trimmedRange] default: return [] } } } // Default implementation used by all the nodes declared below private extension SelectionRangeProvider { func calculateSelectionRanges(position: AbsolutePosition) -> [Range] { return [self.trimmedRange] } } extension AccessorBlockSyntax: SelectionRangeProvider {} extension AccessorDeclSyntax: SelectionRangeProvider {} extension ArrayElementListSyntax: SelectionRangeProvider {} extension ArrayExprSyntax: SelectionRangeProvider {} extension AsExprSyntax: SelectionRangeProvider {} extension AssociatedTypeDeclSyntax: SelectionRangeProvider {} extension AttributedTypeSyntax: SelectionRangeProvider {} extension AttributeListSyntax: SelectionRangeProvider {} extension AttributeSyntax: SelectionRangeProvider {} extension AvailabilityArgumentListSyntax: SelectionRangeProvider {} extension AwaitExprSyntax: SelectionRangeProvider {} extension ClassDeclSyntax: SelectionRangeProvider {} extension ClosureShorthandParameterListSyntax: SelectionRangeProvider {} extension ClosureShorthandParameterSyntax: SelectionRangeProvider {} extension ClosureSignatureSyntax: SelectionRangeProvider {} extension CompositionTypeElementListSyntax: SelectionRangeProvider {} extension ConditionElementListSyntax: SelectionRangeProvider {} extension ConditionElementSyntax: SelectionRangeProvider {} extension ConformanceRequirementSyntax: SelectionRangeProvider {} extension DeclReferenceExprSyntax: SelectionRangeProvider {} extension DeinitializerDeclSyntax: SelectionRangeProvider {} extension DictionaryElementListSyntax: SelectionRangeProvider {} extension DictionaryExprSyntax: SelectionRangeProvider {} extension DoStmtSyntax: SelectionRangeProvider {} extension EnumCaseDeclSyntax: SelectionRangeProvider {} extension EnumCaseElementListSyntax: SelectionRangeProvider {} extension ExpressionSegmentSyntax: SelectionRangeProvider {} extension FunctionEffectSpecifiersSyntax: SelectionRangeProvider {} extension FunctionParameterListSyntax: SelectionRangeProvider {} extension GenericParameterClauseSyntax: SelectionRangeProvider {} extension GenericParameterListSyntax: SelectionRangeProvider {} extension GenericRequirementSyntax: SelectionRangeProvider {} extension GenericWhereClauseSyntax: SelectionRangeProvider {} extension GuardStmtSyntax: SelectionRangeProvider {} extension ImplicitlyUnwrappedOptionalTypeSyntax: SelectionRangeProvider {} extension InheritanceClauseSyntax: SelectionRangeProvider {} extension InheritedTypeListSyntax: SelectionRangeProvider {} extension InitializerDeclSyntax: SelectionRangeProvider {} extension KeyPathExprSyntax: SelectionRangeProvider {} extension LabeledExprListSyntax: SelectionRangeProvider {} extension MacroExpansionExprSyntax: SelectionRangeProvider {} extension OperatorDeclSyntax: SelectionRangeProvider {} extension PlatformVersionSyntax: SelectionRangeProvider {} extension PrefixOperatorExprSyntax: SelectionRangeProvider {} extension RepeatStmtSyntax: SelectionRangeProvider {} extension ReturnClauseSyntax: SelectionRangeProvider {} extension ReturnStmtSyntax: SelectionRangeProvider {} extension StringLiteralExprSyntax: SelectionRangeProvider {} extension StringLiteralSegmentListSyntax: SelectionRangeProvider {} extension SubscriptDeclSyntax: SelectionRangeProvider {} extension SwitchCaseItemListSyntax: SelectionRangeProvider {} extension SwitchCaseItemSyntax: SelectionRangeProvider {} extension SwitchCaseSyntax: SelectionRangeProvider {} extension SwitchExprSyntax: SelectionRangeProvider {} extension ThrowStmtSyntax: SelectionRangeProvider {} extension TryExprSyntax: SelectionRangeProvider {} extension TupleExprSyntax: SelectionRangeProvider {} extension TuplePatternElementListSyntax: SelectionRangeProvider {} extension TuplePatternSyntax: SelectionRangeProvider {} extension TypeAnnotationSyntax: SelectionRangeProvider {} extension UnresolvedAsExprSyntax: SelectionRangeProvider {} extension VariableDeclSyntax: SelectionRangeProvider {} extension VersionTupleSyntax: SelectionRangeProvider {} extension WhileStmtSyntax: SelectionRangeProvider {}