mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
The basic idea is that a `sourcekit-lsp://swift-macro-expansion` URL should have sufficient information to reconstruct the contents of that macro buffer without relying on any state in SourceKit-LSP. The benefit of not having any cross-request state in SourceKit-LSP is that an editor might can send the `workspace/getReferenceDocument` request at any time and it will succeed independent of the previous requests. Furthermore, we can always get the contents of the macro expansion to form a `DocumentSnapshot`, which can be used to provide semantic functionality inside macro expansion buffers. To do that, the `sourcekit-lsp:` URL scheme was changed to have a parent instead of a `primary`, which is the URI of the document that the buffer was expanded from. For nested macro expansions, this will be a `sourcekit-lsp://swift-macro-expansion` URL itself. With that parent, we can reconstruct the macro expansion chain all the way from the primary source file. To avoid sending the same expand macro request to sourcekitd all the time, we introduce `MacroExpansionManager`, which caches the last 10 macro expansions. `SwiftLanguageService` now has a `latestSnapshot` method that returns the contents of the reference document when asked for a reference document URL and only consults the document manager for other URIs. To support semantic functionality in macro expansion buffers, we need to call that `latestSnapshot` method so we have a document snapshot of the macro expansion buffer for position conversions and pass the following to the sourcekitd requests. ``` keys.sourceFile: snapshot.uri.sourcekitdSourceFile, keys.primaryFile: snapshot.uri.primaryFile?.pseudoPath, ``` We should consider if there’s a way to make the `latestSnapshot` method on `documentManager` less accessible so that the method which also returns snapshots for reference documents is the one being used by default. Co-Authored-By: Lokesh T R <lokesh.t.r.official@gmail.com>
111 lines
3.4 KiB
Swift
111 lines
3.4 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
|
|
package struct SemanticRefactorCommand: SwiftCommand {
|
|
typealias Response = SemanticRefactoring
|
|
|
|
package static let identifier: String = "semantic.refactor.command"
|
|
|
|
/// The name of this refactoring action.
|
|
package var title: String
|
|
|
|
/// The sourcekitd identifier of the refactoring action.
|
|
package var actionString: String
|
|
|
|
/// The range to refactor.
|
|
package var positionRange: Range<Position>
|
|
|
|
/// The text document related to the refactoring action.
|
|
package var textDocument: TextDocumentIdentifier
|
|
|
|
package init?(fromLSPDictionary dictionary: [String: LSPAny]) {
|
|
guard case .dictionary(let documentDict)? = dictionary[CodingKeys.textDocument.stringValue],
|
|
case .string(let title)? = dictionary[CodingKeys.title.stringValue],
|
|
case .string(let actionString)? = dictionary[CodingKeys.actionString.stringValue],
|
|
case .dictionary(let rangeDict)? = dictionary[CodingKeys.positionRange.stringValue]
|
|
else {
|
|
return nil
|
|
}
|
|
guard let positionRange = Range<Position>(fromLSPDictionary: rangeDict),
|
|
let textDocument = TextDocumentIdentifier(fromLSPDictionary: documentDict)
|
|
else {
|
|
return nil
|
|
}
|
|
self.init(
|
|
title: title,
|
|
actionString: actionString,
|
|
positionRange: positionRange,
|
|
textDocument: textDocument
|
|
)
|
|
}
|
|
|
|
package init(
|
|
title: String,
|
|
actionString: String,
|
|
positionRange: Range<Position>,
|
|
textDocument: TextDocumentIdentifier
|
|
) {
|
|
self.title = title
|
|
self.actionString = actionString
|
|
self.positionRange = positionRange
|
|
self.textDocument = textDocument
|
|
}
|
|
|
|
package func encodeToLSPAny() -> LSPAny {
|
|
return .dictionary([
|
|
CodingKeys.title.stringValue: .string(title),
|
|
CodingKeys.actionString.stringValue: .string(actionString),
|
|
CodingKeys.positionRange.stringValue: positionRange.encodeToLSPAny(),
|
|
CodingKeys.textDocument.stringValue: textDocument.encodeToLSPAny(),
|
|
])
|
|
}
|
|
}
|
|
|
|
extension Array where Element == SemanticRefactorCommand {
|
|
init?(
|
|
array: SKDResponseArray?,
|
|
range: Range<Position>,
|
|
textDocument: TextDocumentIdentifier,
|
|
_ keys: sourcekitd_api_keys,
|
|
_ api: sourcekitd_api_functions_t
|
|
) {
|
|
guard let results = array else {
|
|
return nil
|
|
}
|
|
var commands: [SemanticRefactorCommand] = []
|
|
results.forEach { _, value in
|
|
if let name: String = value[keys.actionName],
|
|
let actionuid: sourcekitd_api_uid_t = value[keys.actionUID],
|
|
let ptr = api.uid_get_string_ptr(actionuid)
|
|
{
|
|
let actionName = String(cString: ptr)
|
|
guard !actionName.hasPrefix("source.refactoring.kind.rename.") else {
|
|
return true
|
|
}
|
|
commands.append(
|
|
SemanticRefactorCommand(
|
|
title: name,
|
|
actionString: actionName,
|
|
positionRange: range,
|
|
textDocument: textDocument
|
|
)
|
|
)
|
|
}
|
|
return true
|
|
}
|
|
self = commands
|
|
}
|
|
}
|