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 141 142 143
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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 LanguageServerProtocol
import SourceKitD
/// 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.
struct SemanticRefactoring {
/// The title of the refactoring action.
var title: String
/// The resulting `WorkspaceEdit` of a `semantic_refactoring` request.
var edit: WorkspaceEdit
init(_ title: String, _ edit: WorkspaceEdit) {
self.title = title
self.edit = edit
}
/// Create a `SemanticRefactoring` from a sourcekitd 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 categorizedEdits: SKDResponseArray = dict[keys.categorizedEdits] else {
return nil
}
var textEdits = [TextEdit]()
categorizedEdits.forEach { _, value in
guard let edits: SKDResponseArray = value[keys.edits] else {
return false
}
edits.forEach { _, value in
// The LSP is zero based, but semantic_refactoring is one based.
guard let startLine: Int = value[keys.line],
let startColumn: Int = value[keys.column],
let endLine: Int = value[keys.endLine],
let endColumn: Int = value[keys.endColumn],
let text: String = value[keys.text]
else {
return true // continue
}
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)
textEdits.append(TextEdit(range: startPosition..<endPosition, newText: textWithSnippets))
return true
}
return true
}
guard textEdits.isEmpty == false else {
return nil
}
self.title = title
self.edit = WorkspaceEdit(changes: [snapshot.uri: textEdits])
}
}
/// An error from a semantic refactoring request.
enum SemanticRefactoringError: Error {
/// The underlying sourcekitd request failed with the given error.
case responseError(ResponseError)
/// The underlying sourcekitd reported no edits for this action.
case noEditsNeeded(DocumentURI)
}
extension SemanticRefactoringError: CustomStringConvertible {
var description: String {
switch self {
case .responseError(let error):
return "\(error)"
case .noEditsNeeded(let url):
return "no edits reported for semantic refactoring action for url \(url)"
}
}
}
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 refactor `Command` that triggered this request.
func semanticRefactoring(
_ 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)?.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
}
}
|