1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
//===----------------------------------------------------------------------===//
//
// 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
import LanguageServerProtocol
import SKLogging
import SKUtilities
import SourceKitD
protocol RefactoringResponse {
init(title: String, uri: DocumentURI, refactoringEdits: [RefactoringEdit])
}
extension Array<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 = []
categorizedEdits.forEach { _, categorizedEdit in
guard let edits: SKDResponseArray = categorizedEdit[keys.edits] else {
logger.fault("edits doesn't exist in categorizedEdit dictionary")
return true
}
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..<endPosition,
newText: textWithSnippets,
bufferName: edit[keys.bufferName]
)
)
return true
}
return true
}
guard !self.isEmpty else {
logger.error("No refactoring edits found")
return nil
}
}
}
extension RefactoringResponse {
/// Create an instance of `RefactoringResponse` from a sourcekitd semantic
/// refactoring response dictionary, if possible.
///
/// - Parameters:
/// - title: The title of the refactoring action.
/// - dict: Response dictionary to extract information from.
/// - snapshot: The snapshot that triggered the `semantic_refactoring` request.
/// - keys: The sourcekitd key set to use for looking up into `dict`.
init?(_ title: String, _ dict: SKDResponseDictionary, _ snapshot: DocumentSnapshot, _ keys: sourcekitd_api_keys) {
guard let refactoringEdits = [RefactoringEdit](dict, snapshot, keys) else {
return nil
}
self.init(title: title, uri: snapshot.uri, refactoringEdits: refactoringEdits)
}
}
extension SwiftLanguageService {
/// Provides detailed information about the result of a specific refactoring
/// operation.
///
/// Wraps the information returned by sourcekitd's `semantic_refactoring`
/// request, such as the necessary edits and placeholder locations.
///
/// - Parameters:
/// - refactorCommand: The semantic `RefactorCommand` that triggered this request.
/// - Returns: The response of the refactoring
func refactoring(
_ refactorCommand: SemanticRefactorCommand
) async throws -> 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.request: self.requests.semanticRefactoring,
// 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.buildSettings(for: snapshot.uri, fallbackAfterTimeout: true)?.compilerArgs
as [SKDRequestValue]?,
])
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else {
throw SemanticRefactoringError.noEditsNeeded(uri)
}
return refactor
}
}
|