//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2024 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 // //===----------------------------------------------------------------------===// import Csourcekitd @_spi(SourceKitLSP) import LanguageServerProtocol @_spi(SourceKitLSP) import SKLogging import SKUtilities import SourceKitD import SourceKitLSP protocol RefactoringResponse { init(title: String, uri: DocumentURI, refactoringEdits: [RefactoringEdit]) } extension [RefactoringEdit] { init?(_ dict: SKDResponseDictionary, _ snapshot: DocumentSnapshot, _ keys: sourcekitd_api_keys) { guard let categorizedEdits: SKDResponseArray = dict[keys.categorizedEdits] else { logger.fault("categorizedEdits doesn't exist in response dictionary") return nil } self = [] // swift-format-ignore: ReplaceForEachWithForLoop // Reference is to `SKDResponseArray.forEach`, not `Array.forEach`. categorizedEdits.forEach { _, categorizedEdit in guard let edits: SKDResponseArray = categorizedEdit[keys.edits] else { logger.fault("edits doesn't exist in categorizedEdit dictionary") return true } // swift-format-ignore: ReplaceForEachWithForLoop // Reference is to `SKDResponseArray.forEach`, not `Array.forEach`. edits.forEach { _, edit in guard let startLine: Int = edit[keys.line], let startColumn: Int = edit[keys.column], let endLine: Int = edit[keys.endLine], let endColumn: Int = edit[keys.endColumn], let text: String = edit[keys.text] else { logger.fault("Failed to deserialise edit dictionary containing values: \(edit)") return true // continue } // The LSP is zero based, but semantic_refactoring is one based. let startPosition = snapshot.positionOf( zeroBasedLine: startLine - 1, utf8Column: startColumn - 1 ) let endPosition = snapshot.positionOf( zeroBasedLine: endLine - 1, utf8Column: endColumn - 1 ) // Snippets are only supported in code completion. // Remove SourceKit placeholders in refactoring actions because they // can't be represented in the editor properly. let textWithSnippets = rewriteSourceKitPlaceholders(in: text, clientSupportsSnippets: false) self.append( RefactoringEdit( range: startPosition.. SemanticRefactoring { let keys = self.keys let uri = refactorCommand.textDocument.uri let snapshot = try self.documentManager.latestSnapshot(uri) let line = refactorCommand.positionRange.lowerBound.line let utf16Column = refactorCommand.positionRange.lowerBound.utf16index let utf8Column = snapshot.lineTable.utf8ColumnAt(line: line, utf16Column: utf16Column) let skreq = sourcekitd.dictionary([ keys.cancelOnSubsequentRequest: 0, // Preferred name for e.g. an extracted variable. // Empty string means sourcekitd chooses a name automatically. keys.name: "", keys.sourceFile: uri.pseudoPath, // LSP is zero based, but this request is 1 based. keys.line: line + 1, keys.column: utf8Column + 1, keys.length: snapshot.utf8OffsetRange(of: refactorCommand.positionRange).count, keys.actionUID: self.sourcekitd.api.uid_get_from_cstr(refactorCommand.actionString)!, keys.compilerArgs: await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: true)?.compilerArgs as [any SKDRequestValue]?, ]) let dict = try await send(sourcekitdRequest: \.semanticRefactoring, skreq, snapshot: snapshot) guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else { throw SemanticRefactoringError.noEditsNeeded(uri) } return refactor } }