Skip to content

Commit 664cf60

Browse files
committed
Merge tag '0.9.0' into develop
2 parents 3bfca83 + f359dc7 commit 664cf60

12 files changed

Lines changed: 88 additions & 32 deletions

File tree

Config.xcconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ SLASH = /
33

44
BUNDLE_IDENTIFIER_BASE = com.intii.CopilotForXcode
55
EXTENSION_BUNDLE_NAME = Copilot
6-
SPARKLE_FEED_URL = https:$(SLASH)$(SLASH)github.com/intitni/CopilotForXcode/blob/main/appcast.xml
6+
SPARKLE_FEED_URL = https:$(SLASH)$(SLASH)raw.githubusercontent.com/intitni/CopilotForXcode/main/appcast.xml
77
SPARKLE_PUBLIC_KEY = WDzm5GHnc6c8kjeJEgX5GuGiPpW6Lc/ovGjLnrrZvPY=

Core/Sources/Environment/Environment.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ public enum Environment {
138138

139139
try await runAppleScript(appleScript)
140140
}
141+
142+
public static var makeXcodeActive: () async throws -> Void = {
143+
let appleScript = """
144+
tell application "Xcode"
145+
activate
146+
end tell
147+
"""
148+
try await runAppleScript(appleScript)
149+
}
141150
}
142151

143152
@discardableResult

Core/Sources/Service/SuggestionCommandHandler/WindowBaseCommandHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ struct WindowBaseCommandHandler: SuggestionCommandHandler {
279279
history.append(.init(
280280
role: .user,
281281
content: "",
282-
summary: "Chat about selected code from `\(selection.start.line + 1):\(selection.start.character + 1)` to `\(selection.end.line + 1):\(selection.end.character)`.\nThe code will persisted in the conversation."
282+
summary: "Chat about selected code from `\(selection.start.line + 1):\(selection.start.character + 1)` to `\(selection.end.line + 1):\(selection.end.character)`.\nThe code will persist in the conversation."
283283
))
284284
}
285285
}

Core/Sources/SuggestionWidget/Styles.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import AppKit
22
import SwiftUI
33

44
enum Style {
5-
static let panelHeight: Double = 300
6-
static let panelWidth: Double = 450
5+
static let panelHeight: Double = 304
6+
static let panelWidth: Double = 454
77
static let widgetHeight: Double = 30
88
static var widgetWidth: Double { widgetHeight }
99
static let widgetPadding: Double = 4

Core/Sources/SuggestionWidget/SuggestionPanelContent/ChatPanel.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ struct ChatPanel: View {
2828
chat.close()
2929
}) {
3030
Image(systemName: "xmark")
31-
.padding(8)
32-
.background(.regularMaterial, in: Circle())
3331
.padding(4)
32+
.background(.regularMaterial, in: Circle())
33+
.padding(2)
3434
.foregroundStyle(.secondary)
3535
}
3636
.buttonStyle(.plain)
@@ -112,6 +112,7 @@ struct ChatPanelMessages: View {
112112
)
113113
.xcodeStyleFrame()
114114
.rotationEffect(Angle(degrees: 180))
115+
115116
}
116117
}
117118
}

Core/Sources/SuggestionWidget/SuggestionPanelView.swift

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Environment
12
import SwiftUI
23

34
@MainActor
@@ -40,15 +41,8 @@ final class SuggestionPanelViewModel: ObservableObject {
4041

4142
@Published var activeTab: ActiveTab {
4243
didSet {
43-
#warning("""
44-
TODO: There should be a better way for that
45-
Currently, we have to make the app an accessory so that we can type things in the chat mode.
46-
But in other modes, we want to keep it prohibited so the helper app won't take over the focus.
47-
""")
48-
if case .chat = activeTab {
49-
NSApp.setActivationPolicy(.accessory)
50-
} else {
51-
NSApp.setActivationPolicy(.prohibited)
44+
if activeTab != oldValue {
45+
onActiveTabChanged?(activeTab)
5246
}
5347
}
5448
}
@@ -61,6 +55,7 @@ final class SuggestionPanelViewModel: ObservableObject {
6155
var onRejectButtonTapped: (() -> Void)?
6256
var onPreviousButtonTapped: (() -> Void)?
6357
var onNextButtonTapped: (() -> Void)?
58+
var onActiveTabChanged: ((ActiveTab) -> Void)?
6459

6560
public init(
6661
content: Content? = nil,
@@ -71,7 +66,8 @@ final class SuggestionPanelViewModel: ObservableObject {
7166
onAcceptButtonTapped: (() -> Void)? = nil,
7267
onRejectButtonTapped: (() -> Void)? = nil,
7368
onPreviousButtonTapped: (() -> Void)? = nil,
74-
onNextButtonTapped: (() -> Void)? = nil
69+
onNextButtonTapped: (() -> Void)? = nil,
70+
onActiveTabChanged: ((ActiveTab) -> Void)? = nil
7571
) {
7672
self.content = content
7773
self.chat = chat
@@ -82,6 +78,7 @@ final class SuggestionPanelViewModel: ObservableObject {
8278
self.onRejectButtonTapped = onRejectButtonTapped
8379
self.onPreviousButtonTapped = onPreviousButtonTapped
8480
self.onNextButtonTapped = onNextButtonTapped
81+
self.onActiveTabChanged = onActiveTabChanged
8582
}
8683

8784
func adjustActiveTabAndShowHideIfNeeded(tab: ActiveTab) {
@@ -90,11 +87,13 @@ final class SuggestionPanelViewModel: ObservableObject {
9087
if content != nil {
9188
activeTab = .suggestion
9289
isPanelDisplayed = true
90+
return
9391
}
9492
case .chat:
9593
if chat != nil {
9694
activeTab = .chat
9795
isPanelDisplayed = true
96+
return
9897
}
9998
}
10099

@@ -158,6 +157,7 @@ struct SuggestionPanelView: View {
158157
Color.userChatContentBackground,
159158
in: RoundedRectangle(cornerRadius: 8, style: .continuous)
160159
)
160+
.fixedSize(horizontal: false, vertical: true)
161161
})
162162
.buttonStyle(.plain)
163163
.xcodeStyleFrame()
@@ -187,6 +187,7 @@ struct SuggestionPanelView: View {
187187
Color.userChatContentBackground,
188188
in: RoundedRectangle(cornerRadius: 8, style: .continuous)
189189
)
190+
.fixedSize(horizontal: false, vertical: true)
190191
})
191192
.buttonStyle(.plain)
192193
.xcodeStyleFrame()
@@ -211,6 +212,9 @@ struct SuggestionPanelView: View {
211212
.animation(.easeInOut(duration: 0.2), value: viewModel.chat)
212213
.animation(.easeInOut(duration: 0.2), value: viewModel.activeTab)
213214
.animation(.easeInOut(duration: 0.2), value: viewModel.isPanelDisplayed)
215+
.shadow(color: .black.opacity(0.1), radius: 2)
216+
.padding(2)
217+
.frame(maxWidth: Style.panelWidth, maxHeight: Style.panelHeight)
214218
}
215219
}
216220

@@ -221,7 +225,7 @@ struct CommandButtonStyle: ButtonStyle {
221225
configuration.label
222226
.padding(.vertical, 4)
223227
.padding(.horizontal, 8)
224-
.foregroundColor(.white)
228+
.foregroundColor(.white)
225229
.background(
226230
RoundedRectangle(cornerRadius: 4, style: .continuous)
227231
.fill(color.opacity(configuration.isPressed ? 0.8 : 1))

Core/Sources/SuggestionWidget/SuggestionWidgetController.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,24 @@ public final class SuggestionWidgetController {
197197
context: nil
198198
)
199199
}
200+
201+
Task { @MainActor in
202+
suggestionPanelViewModel.onActiveTabChanged = { [weak self] activeTab in
203+
#warning("""
204+
TODO: There should be a better way for that
205+
Currently, we have to make the app an accessory so that we can type things in the chat mode.
206+
But in other modes, we want to keep it prohibited so the helper app won't take over the focus.
207+
""")
208+
if case .chat = activeTab {
209+
NSApp.setActivationPolicy(.accessory)
210+
} else {
211+
Task {
212+
try await Environment.makeXcodeActive()
213+
NSApp.setActivationPolicy(.prohibited)
214+
}
215+
}
216+
}
217+
}
200218
}
201219
}
202220
@@ -384,7 +402,8 @@ extension SuggestionWidgetController {
384402
activeScreen: firstScreen
385403
)
386404
widgetWindow.setFrame(result.widgetFrame, display: false, animate: animated)
387-
panelWindow.setFrame(result.panelFrame, display: false, animate: animated)
405+
panelWindow.setFrame(result.panelFrame, display: true, animate: animated)
406+
388407
suggestionPanelViewModel.alignTopToAnchor = result.alignPanelTopToAnchor
389408
case .alignToTextCursor:
390409
let result = UpdateLocationStrategy.AlignToTextCursor().framesForWindows(

EditorExtension/ChatWithSelection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Foundation
44
import XcodeKit
55

66
class ChatWithSelectionCommand: NSObject, XCSourceEditorCommand, CommandType {
7-
var name: String { "Chat With Selection" }
7+
var name: String { "Chat with Selection" }
88

99
func perform(
1010
with invocation: XCSourceEditorCommandInvocation,

ExtensionService/AppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
5454

5555
#if DEBUG
5656
let copilotName = NSMenuItem(
57-
title: "Copilot for Xcode",
57+
title: "Copilot for Xcode - DEBUG",
5858
action: nil,
5959
keyEquivalent: ""
6060
)
6161
#else
6262
let copilotName = NSMenuItem(
63-
title: "Copilot for Xcode - DEBUG",
63+
title: "Copilot for Xcode",
6464
action: nil,
6565
keyEquivalent: ""
6666
)

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![Screenshot](/Screenshot.png)
44

5-
Copilot for Xcode is an Xcode Source Editor Extension that provides Github Copilot support for Xcode. It uses the LSP provided through [Copilot.vim](https://github.com/github/copilot.vim/tree/release/copilot/dist) to generate suggestions and displays them as comments or in a separate window.
5+
Copilot for Xcode is an Xcode Source Editor Extension that provides Github Copilot and ChatGPT support for Xcode. It uses the LSP provided through [Copilot.vim](https://github.com/github/copilot.vim/tree/release/copilot/dist) to generate suggestions and displays them as comments or in a separate window.
66

77
Thanks to [LSP-copilot](https://github.com/TerminalFi/LSP-copilot) for showing the way to interact with Copilot. And thanks to [LanguageClient](https://github.com/ChimeHQ/LanguageClient) for the Language Server Protocol support in Swift.
88

@@ -31,7 +31,8 @@ For development instruction, check [Development.md](DEVELOPMENT.md).
3131

3232
- [Node](https://nodejs.org/) installed to run the Copilot LSP.
3333
- Public network connection.
34-
- Active GitHub Copilot subscription.
34+
- Active GitHub Copilot subscription (to use suggestion features).
35+
- Valid OpenAI API key (to use chat features).
3536

3637
## Permissions Required
3738

@@ -94,7 +95,7 @@ If the app was installed via Homebrew, you can update it by running:
9495
brew upgrade --cask copilot-for-xcode
9596
```
9697

97-
Alternatively, You can download the latest version manually from the latest [release](https://github.com/intitni/CopilotForXcode/releases).
98+
Alternatively, You can use the in-app updater or download the latest version manually from the latest [release](https://github.com/intitni/CopilotForXcode/releases).
9899

99100
If you are upgrading from a version lower than **0.7.0**, please run `Copilot for Xcode.app` at least once to let it set up the new launch agent for you and re-grant the permissions according to the new rules.
100101

@@ -104,26 +105,27 @@ If you find that some of the features are no longer working, please first try re
104105

105106
## Commands
106107

108+
### Suggestion
109+
107110
- Get Suggestions: Get suggestions for the editing file at the current cursor position.
108111
- Next Suggestion: If there is more than one suggestion, switch to the next one.
109112
- Previous Suggestion: If there is more than one suggestion, switch to the previous one.
110113
- Accept Suggestion: Add the suggestion to the code.
111114
- Reject Suggestion: Remove the suggestion comments.
112115
- Toggle Real-time Suggestions: When turn on, Copilot will auto-insert suggestion comments to your code while editing.
113116
- Real-time Suggestions: Call only by Copilot for Xcode. When suggestions are successfully fetched, Copilot for Xcode will run this command to present the suggestions.
114-
- Prefetch Suggestions: Call only by Copilot for Xcode. In the background, Copilot for Xcode will occasionally run this command to prefetch real-time suggestions.
117+
- Prefetch Suggestions: Call only by Copilot for Xcode. In the background, Copilot for Xcode will occasionally run this command to prefetch real-time suggestions.
115118

116119
**About real-time suggestions**
117120

118-
The on/off state is persisted, so be sure to turn it off manually when you no longer want it. When real-time suggestion is turned on, a dot will show up next to the text cursor.
119-
120121
Whenever you stop typing for a few milliseconds, the app will automatically fetch suggestions for you, you can cancel this by clicking the mouse, or pressing **Escape** or the **arrow keys**.
121122

122-
When a fetch occurs, the dot will play an animation. If you don't see it, your permissions may not be set correctly.
123+
### Chat
123124

124-
The implementation won't feel as smooth as that of VSCode. The magic behind it is that it will keep calling the command from the menu when you are not typing or clicking the mouse. So it will have to listen to those events, I am not sure if people like it. Hope that next year, Apple can spend some time on Xcode Extensions.
125+
- Chat with Selection: Open a chat window, if there is a selection, the selected code will be added to the prompt.
126+
- Explain Selection: Open a chat window and explain the selected code.
125127

126-
It will be a better experience if you use the "Floating Widget" mode with real-time suggestions turned on.
128+
Chat commands are not available in comment mode.
127129

128130
## Key Bindings
129131

@@ -141,6 +143,8 @@ A [recommended setup](https://github.com/intitni/CopilotForXcode/issues/14) that
141143

142144
Essentially using `⌥⇧` as the "access" key combination for all bindings.
143145

146+
Another convenient method to access commands is by using the `⇧⌘/` shortcut to search for a command in the menu bar.
147+
144148
## Prevent Suggestions Being Committed (in comment mode)
145149

146150
Since the suggestions are presented as comments, they are in your code. If you are not careful enough, they can be committed to your git repo. To avoid that, I would recommend adding a pre-commit git hook to prevent this from happening.

0 commit comments

Comments
 (0)