Skip to content

Commit e34193f

Browse files
committed
Correctly handle custom command when it's not in source editor
1 parent fbfeaf5 commit e34193f

File tree

4 files changed

+112
-22
lines changed

4 files changed

+112
-22
lines changed

Core/Sources/Environment/Environment.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,24 @@ public enum Environment {
105105
}
106106
throw FailedToFetchFileURLError()
107107
}
108+
109+
public static var fetchFocusedElementURI: () async throws -> URL = {
110+
guard let xcode = ActiveApplicationMonitor.activeXcode
111+
?? ActiveApplicationMonitor.latestXcode
112+
else {
113+
throw FailedToFetchFileURLError()
114+
}
115+
116+
let application = AXUIElementCreateApplication(xcode.processIdentifier)
117+
let focusedElement = application.focusedElement
118+
if focusedElement?.description != "Source Editor" {
119+
let window = application.focusedWindow
120+
let id = window?.identifier.hashValue
121+
return URL(fileURLWithPath: "/xcode-focused-element/\(id ?? 0)")
122+
}
123+
124+
return try await fetchCurrentFileURL()
125+
}
108126

109127
public static var createAuthService: () -> CopilotAuthServiceType = {
110128
CopilotAuthService()
@@ -148,6 +166,15 @@ public enum Environment {
148166
.error("Trigger action \(name) failed: \(error.localizedDescription)")
149167
throw error
150168
}
169+
} else {
170+
struct CantRunCommand: Error, LocalizedError {
171+
let name: String
172+
var errorDescription: String? {
173+
"Can't run command \(name)."
174+
}
175+
}
176+
177+
throw CantRunCommand(name: name)
151178
}
152179
} else {
153180
/// check if menu is open, if not, click the menu item.

Core/Sources/Service/SuggestionCommandHandler/PseudoCommandHandler.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,26 @@ struct PseudoCommandHandler {
6969
}
7070

7171
func handleCustomCommand(_ command: CustomCommand) async {
72-
guard let editor = await getEditorContent(sourceEditor: nil)
73-
else {
72+
guard let editor = await {
73+
if let it = await getEditorContent(sourceEditor: nil) {
74+
return it
75+
}
76+
switch command.feature {
77+
case .customChat:
78+
return .init(
79+
content: "",
80+
lines: [],
81+
uti: "",
82+
cursorPosition: .outOfScope,
83+
selections: [],
84+
tabSize: 0,
85+
indentSize: 0,
86+
usesTabsForIndentation: false
87+
)
88+
case .chatWithSelection, .promptToCode:
89+
return nil
90+
}
91+
}() else {
7492
do {
7593
try await Environment.triggerAction(command.name)
7694
} catch {

Core/Sources/Service/SuggestionCommandHandler/WindowBaseCommandHandler.swift

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,7 @@ extension WindowBaseCommandHandler {
297297
name: command.name
298298
)
299299
case let .customChat(systemPrompt, prompt):
300-
try await startChatWithSelection(
301-
editor: editor,
300+
try await startChat(
302301
specifiedSystemPrompt: systemPrompt,
303302
extraSystemPrompt: nil,
304303
sendingMessageImmediately: prompt,
@@ -435,7 +434,7 @@ extension WindowBaseCommandHandler {
435434

436435
Task {
437436
let customCommandPrefix = {
438-
if let name { return "[\(name)]" }
437+
if let name { return "[\(name)] " }
439438
return ""
440439
}()
441440

@@ -444,23 +443,23 @@ extension WindowBaseCommandHandler {
444443
history.append(.init(
445444
role: .assistant,
446445
content: "",
447-
summary: "\(customCommandPrefix) System prompt is updated."
446+
summary: "\(customCommandPrefix)System prompt is updated."
448447
))
449448
}
450449
} else if !code.isEmpty, let selection = editor.selections.last {
451450
await chat.chatGPTService.mutateHistory { history in
452451
history.append(.init(
453452
role: .assistant,
454453
content: "",
455-
summary: "\(customCommandPrefix) Chatting about selected code in `\(fileURL.lastPathComponent)` from `\(selection.start.line + 1):\(selection.start.character + 1)` to `\(selection.end.line + 1):\(selection.end.character)`.\nThe code will persist in the conversation."
454+
summary: "\(customCommandPrefix)Chatting about selected code in `\(fileURL.lastPathComponent)` from `\(selection.start.line + 1):\(selection.start.character + 1)` to `\(selection.end.line + 1):\(selection.end.character)`.\nThe code will persist in the conversation."
456455
))
457456
}
458457
} else if !customCommandPrefix.isEmpty {
459458
await chat.chatGPTService.mutateHistory { history in
460459
history.append(.init(
461460
role: .assistant,
462461
content: "",
463-
summary: "\(customCommandPrefix) System prompt is updated."
462+
summary: "\(customCommandPrefix)System prompt is updated."
464463
))
465464
}
466465
}
@@ -472,4 +471,63 @@ extension WindowBaseCommandHandler {
472471

473472
presenter.presentChatRoom(fileURL: fileURL)
474473
}
474+
475+
private func startChat(
476+
specifiedSystemPrompt: String?,
477+
extraSystemPrompt: String?,
478+
sendingMessageImmediately: String?,
479+
name: String?
480+
) async throws {
481+
presenter.markAsProcessing(true)
482+
defer { presenter.markAsProcessing(false) }
483+
484+
let focusedElementURI = try await Environment.fetchFocusedElementURI()
485+
let language = UserDefaults.shared.value(for: \.chatGPTLanguage)
486+
487+
var systemPrompt = specifiedSystemPrompt ?? """
488+
\(language.isEmpty ? "" : "You must always reply in \(language)")
489+
You are a senior programmer, you will answer my questions concisely. If you are replying with code, embed the code in a code block in markdown.
490+
491+
You don't have any code in advance, ask me to provide it when needed.
492+
"""
493+
494+
if let extraSystemPrompt {
495+
systemPrompt += "\n\(extraSystemPrompt)"
496+
}
497+
498+
let chat = WidgetDataSource.shared.createChatIfNeeded(for: focusedElementURI)
499+
500+
await chat.mutateSystemPrompt(systemPrompt)
501+
502+
Task {
503+
let customCommandPrefix = {
504+
if let name { return "[\(name)] " }
505+
return ""
506+
}()
507+
508+
if specifiedSystemPrompt != nil {
509+
await chat.chatGPTService.mutateHistory { history in
510+
history.append(.init(
511+
role: .assistant,
512+
content: "",
513+
summary: "\(customCommandPrefix)System prompt is updated."
514+
))
515+
}
516+
} else if !customCommandPrefix.isEmpty {
517+
await chat.chatGPTService.mutateHistory { history in
518+
history.append(.init(
519+
role: .assistant,
520+
content: "",
521+
summary: "\(customCommandPrefix)System prompt is updated."
522+
))
523+
}
524+
}
525+
526+
if let sendingMessageImmediately, !sendingMessageImmediately.isEmpty {
527+
try await chat.send(content: sendingMessageImmediately)
528+
}
529+
}
530+
531+
presenter.presentChatRoom(fileURL: focusedElementURI)
532+
}
475533
}

Core/Sources/SuggestionWidget/SuggestionWidgetController.swift

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -374,20 +374,7 @@ extension SuggestionWidgetController {
374374
observeEditorChangeIfNeeded(app)
375375

376376
guard let fileURL = await {
377-
// if it's not editor, use the window id as url.
378-
if let activeNonEditorWindow = {
379-
let application = AXUIElementCreateApplication(app.processIdentifier)
380-
let focusedElement = application.focusedElement
381-
if focusedElement?.description != "Source Editor" {
382-
return application.focusedWindow
383-
}
384-
return nil
385-
}() {
386-
let id = activeNonEditorWindow.identifier.hashValue
387-
return URL(fileURLWithPath: "/xcode-focused-element/\(id)")
388-
}
389-
390-
return try? await Environment.fetchCurrentFileURL()
377+
return try? await Environment.fetchFocusedElementURI()
391378
}() else {
392379
continue
393380
}

0 commit comments

Comments
 (0)