Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sources/SourceKitD/sourcekitd_uids.swift
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,8 @@ package struct sourcekitd_api_requests {
package let findLocalRenameRanges: sourcekitd_api_uid_t
/// `source.request.semantic.refactoring`
package let semanticRefactoring: sourcekitd_api_uid_t
/// `source.request.objc.selector`
package let objcSelector: sourcekitd_api_uid_t
/// `source.request.enable-compile-notifications`
package let enableCompileNotifications: sourcekitd_api_uid_t
/// `source.request.test_notification`
Expand Down Expand Up @@ -951,6 +953,7 @@ package struct sourcekitd_api_requests {
findRenameRanges = api.uid_get_from_cstr("source.request.find-syntactic-rename-ranges")!
findLocalRenameRanges = api.uid_get_from_cstr("source.request.find-local-rename-ranges")!
semanticRefactoring = api.uid_get_from_cstr("source.request.semantic.refactoring")!
objcSelector = api.uid_get_from_cstr("source.request.objc.selector")!
enableCompileNotifications = api.uid_get_from_cstr("source.request.enable-compile-notifications")!
testNotification = api.uid_get_from_cstr("source.request.test_notification")!
collectExpressionType = api.uid_get_from_cstr("source.request.expression.type")!
Expand Down
61 changes: 61 additions & 0 deletions Sources/SwiftLanguageService/CopyObjCSelector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//===----------------------------------------------------------------------===//
//
// 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 Foundation
@_spi(SourceKitLSP) import LanguageServerProtocol
@_spi(SourceKitLSP) import SKLogging
import SourceKitD
import SourceKitLSP

extension SwiftLanguageService {
/// Executes the refactoring-based copy and extracts the selector string without applying edits.
func copyObjCSelector(
_ command: CopyObjCSelectorCommand
) async throws -> LSPAny {
let refactorCommand = SemanticRefactorCommand(
title: command.title,
actionString: command.actionString,
positionRange: command.positionRange,
textDocument: command.textDocument
)

let semanticRefactor = try await self.refactoring(refactorCommand)

guard let edit = semanticRefactor.edit.changes?.first?.value.first else {
throw ResponseError.unknown("No selector found at cursor position")
}

let prefix = "// Objective-C Selector: "
if let range = edit.newText.range(of: prefix) {
let selector = String(edit.newText[range.upperBound...]).trimmingCharacters(in: .whitespacesAndNewlines)
if let sourceKitLSPServer {
// Offer a copy button via showMessageRequest; still return the selector string for clients that ignore it.
let request = ShowMessageRequest(
type: .info,
message: "Objective-C selector: \(selector)",
actions: [MessageActionItem(title: "Copy")]
)
Task {
do {
let response = try await sourceKitLSPServer.sendRequestToClient(request)
logger.debug("Selector2 showMessageRequest response: \(response?.title ?? "<nil>")")
} catch {
logger.debug("Selector2 showMessageRequest failed: \(error.forLogging)")
}
}
}
return .string(selector)
}

throw ResponseError.unknown("Could not extract selector from refactoring result")
}
}
73 changes: 73 additions & 0 deletions Sources/SwiftLanguageService/CopyObjCSelectorCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

@_spi(SourceKitLSP) package import LanguageServerProtocol
import SourceKitD

package struct CopyObjCSelectorCommand: SwiftCommand {
package static let identifier: String = "copy.objc.selector.command"

package var title = "Copy Objective-C Selector"
package var actionString = "source.refactoring.kind.copy.objc.selector"

package var positionRange: Range<Position>
package var textDocument: LanguageServerProtocol.TextDocumentIdentifier

package init(positionRange: Range<Position>, textDocument: LanguageServerProtocol.TextDocumentIdentifier) {
self.positionRange = positionRange
self.textDocument = textDocument
}

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 = LanguageServerProtocol.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: LanguageServerProtocol.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(),
])
}
}
1 change: 1 addition & 0 deletions Sources/SwiftLanguageService/SwiftCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ extension SwiftLanguageService {
[
SemanticRefactorCommand.self,
ExpandMacroCommand.self,
CopyObjCSelectorCommand.self,
].map { (command: any SwiftCommand.Type) in
command.identifier
}
Expand Down
22 changes: 18 additions & 4 deletions Sources/SwiftLanguageService/SwiftLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,8 @@ extension SwiftLanguageService {
skreq.set(self.keys.retrieveRefactorActions, to: 1)
}

let snapshot = try documentManager.latestSnapshot(params.textDocument.uri)

let cursorInfoResponse = try await cursorInfo(
params.textDocument.uri,
params.range,
Expand All @@ -963,13 +965,23 @@ extension SwiftLanguageService {

var canInlineMacro = false

var refactorActions = cursorInfoResponse.refactorActions.compactMap {
let lspCommand = $0.asCommand()
var refactorActions: [CodeAction] = cursorInfoResponse.refactorActions.compactMap { action in
if action.actionString == "source.refactoring.kind.copy.objc.selector" {
let copyCommand = CopyObjCSelectorCommand(
title: "Copy Objective-C Selector",
actionString: action.actionString,
positionRange: params.range,
textDocument: params.textDocument
).asCommand()
return CodeAction(title: "Copy Objective-C Selector", kind: .refactor, command: copyCommand)
}

let lspCommand = action.asCommand()
if !canInlineMacro {
canInlineMacro = $0.actionString == "source.refactoring.kind.inline.macro"
canInlineMacro = action.actionString == "source.refactoring.kind.inline.macro"
}

return CodeAction(title: $0.title, kind: .refactor, command: lspCommand)
return CodeAction(title: action.title, kind: .refactor, command: lspCommand)
}

if canInlineMacro {
Expand Down Expand Up @@ -1096,6 +1108,8 @@ extension SwiftLanguageService {
try await semanticRefactoring(command)
} else if let command = req.swiftCommand(ofType: ExpandMacroCommand.self) {
try await expandMacro(command)
} else if let command = req.swiftCommand(ofType: CopyObjCSelectorCommand.self) {
return try await copyObjCSelector(command)
} else if let command = req.swiftCommand(ofType: RemoveUnusedImportsCommand.self) {
try await removeUnusedImports(command)
} else {
Expand Down