-
Notifications
You must be signed in to change notification settings - Fork 332
Add Show Objective-C Selector code action #2399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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 { | ||
| /// Gets the Objective-C selector for the method at the given position. | ||
| /// | ||
| /// Uses the `source.request.objc.selector` sourcekitd request directly | ||
| /// to retrieve the selector string for @objc methods. | ||
| func showObjCSelector( | ||
| _ command: ShowObjCSelectorCommand | ||
| ) async throws -> LSPAny { | ||
| let keys = self.keys | ||
|
|
||
| let uri = command.textDocument.uri | ||
| let snapshot = try self.documentManager.latestSnapshot(uri) | ||
| let position = command.positionRange.lowerBound | ||
| let offset = snapshot.utf8Offset(of: position) | ||
|
|
||
| let skreq = sourcekitd.dictionary([ | ||
| keys.sourceFile: uri.pseudoPath, | ||
| keys.offset: offset, | ||
| keys.compilerArgs: await self.compileCommand(for: uri, fallbackAfterTimeout: true)?.compilerArgs | ||
| as [any SKDRequestValue]?, | ||
| ]) | ||
|
|
||
| let dict = try await send(sourcekitdRequest: \.objcSelector, skreq, snapshot: snapshot) | ||
|
|
||
| guard let selector: String = dict[keys.text] else { | ||
| throw ResponseError.unknown("Could not retrieve Objective-C selector at cursor position") | ||
| } | ||
|
|
||
| if let sourceKitLSPServer { | ||
| sourceKitLSPServer.sendNotificationToClient( | ||
| ShowMessageNotification(type: .info, message: selector) | ||
| ) | ||
| } | ||
|
|
||
| return .string(selector) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||||||
| //===----------------------------------------------------------------------===// | ||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||
| // 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 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /// Command to show the Objective-C selector for a Swift method marked with @objc. | ||||||||||||||||||||||||||||||||||||||
| package struct ShowObjCSelectorCommand: SwiftCommand { | ||||||||||||||||||||||||||||||||||||||
| package static let identifier: String = "show.objc.selector.command" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| package var title = "Show Objective-C Selector" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| package var positionRange: Range<Position> | ||||||||||||||||||||||||||||||||||||||
| package var textDocument: TextDocumentIdentifier | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| package init(positionRange: Range<Position>, textDocument: 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 .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 | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+41
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be simplified slightly by using
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| self.init( | ||||||||||||||||||||||||||||||||||||||
| title: title, | ||||||||||||||||||||||||||||||||||||||
| positionRange: positionRange, | ||||||||||||||||||||||||||||||||||||||
| textDocument: textDocument | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| package init( | ||||||||||||||||||||||||||||||||||||||
| title: String, | ||||||||||||||||||||||||||||||||||||||
| positionRange: Range<Position>, | ||||||||||||||||||||||||||||||||||||||
| textDocument: TextDocumentIdentifier | ||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||
| self.title = title | ||||||||||||||||||||||||||||||||||||||
| self.positionRange = positionRange | ||||||||||||||||||||||||||||||||||||||
| self.textDocument = textDocument | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| package func encodeToLSPAny() -> LSPAny { | ||||||||||||||||||||||||||||||||||||||
| return .dictionary([ | ||||||||||||||||||||||||||||||||||||||
| CodingKeys.title.stringValue: .string(title), | ||||||||||||||||||||||||||||||||||||||
| CodingKeys.positionRange.stringValue: positionRange.encodeToLSPAny(), | ||||||||||||||||||||||||||||||||||||||
| CodingKeys.textDocument.stringValue: textDocument.encodeToLSPAny(), | ||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -963,13 +963,13 @@ extension SwiftLanguageService { | |
|
|
||
| var canInlineMacro = false | ||
|
|
||
| var refactorActions = cursorInfoResponse.refactorActions.compactMap { | ||
| let lspCommand = $0.asCommand() | ||
| var refactorActions: [CodeAction] = cursorInfoResponse.refactorActions.compactMap { action in | ||
| 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 { | ||
|
|
@@ -979,6 +979,22 @@ extension SwiftLanguageService { | |
| refactorActions.append(CodeAction(title: expandMacroCommand.title, kind: .refactor, command: expandMacroCommand)) | ||
| } | ||
|
|
||
| let methodKinds: [SymbolKind] = [.method, .function, .constructor] | ||
| let isOnMethod = cursorInfoResponse.cursorInfo.contains { cursorInfo in | ||
| if let kind = cursorInfo.symbolInfo.kind { | ||
| return methodKinds.contains(kind) | ||
| } | ||
| return false | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean that we show the refactoring action on every Swift method, even if it isn’t backed by an Objective-C method and thus the Show Objective-C Selector would just fail? Do we have some indication in the cursor info response already to see whether this method is defined in Objective-C / that we have an Objective-C selector? One ugly idea I have is checking whether the USR starts with |
||
|
|
||
| if isOnMethod { | ||
| let showCommand = ShowObjCSelectorCommand( | ||
| positionRange: params.range, | ||
| textDocument: params.textDocument | ||
| ).asCommand() | ||
| refactorActions.append(CodeAction(title: "Show Objective-C Selector", kind: .refactor, command: showCommand)) | ||
| } | ||
|
|
||
| return refactorActions | ||
| } | ||
|
|
||
|
|
@@ -1096,6 +1112,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: ShowObjCSelectorCommand.self) { | ||
| return try await showObjCSelector(command) | ||
| } else if let command = req.swiftCommand(ofType: RemoveUnusedImportsCommand.self) { | ||
| try await removeUnusedImports(command) | ||
| } else { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add the new files to CMakeLists.txt for the Windows build?