Skip to content

Commit 241ae5a

Browse files
committed
Merge branch 'hotfix/tweak-caching'
2 parents 6753da1 + 90906fd commit 241ae5a

File tree

8 files changed

+79
-23
lines changed

8 files changed

+79
-23
lines changed

Copilot for Xcode/DebugView.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import SwiftUI
55
final class DebugSettings: ObservableObject {
66
@AppStorage(\.disableLazyVStack)
77
var disableLazyVStack: Bool
8+
@AppStorage(\.preCacheOnFileOpen)
9+
var preCacheOnFileOpen: Bool
810
init() {}
911
}
1012

@@ -18,6 +20,10 @@ struct DebugSettingsView: View {
1820
Text("Disable LazyVStack")
1921
}
2022
.toggleStyle(.switch)
23+
Toggle(isOn: $settings.preCacheOnFileOpen) {
24+
Text("Cache editor information on file open")
25+
}
26+
.toggleStyle(.switch)
2127
}
2228
}.buttonStyle(.copilot)
2329
}

Core/Sources/AXExtension/AXUIElement.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ public extension AXUIElement {
1111
var value: String {
1212
(try? copyValue(key: kAXValueAttribute)) ?? ""
1313
}
14+
15+
var doubleValue: Double {
16+
(try? copyValue(key: kAXValueAttribute)) ?? 0.0
17+
}
1418

1519
var document: String? {
1620
try? copyValue(key: kAXDocumentAttribute)
@@ -19,6 +23,10 @@ public extension AXUIElement {
1923
var description: String {
2024
(try? copyValue(key: kAXDescriptionAttribute)) ?? ""
2125
}
26+
27+
var isSourceEditor: Bool {
28+
description == "Source Editor"
29+
}
2230

2331
var selectedTextRange: Range<Int>? {
2432
guard let value: AXValue = try? copyValue(key: kAXSelectedTextRangeAttribute)

Core/Sources/Preferences/Keys.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,17 @@ public struct UserDefaultPreferenceKeys {
135135
}
136136

137137
public var disableLazyVStack: FeatureFlags.DisableLazyVStack { .init() }
138+
public var preCacheOnFileOpen: FeatureFlags.PreCacheOnFileOpen { .init() }
138139
}
139140

140141
public enum FeatureFlags {
141142
public struct DisableLazyVStack: UserDefaultPreferenceKey {
142143
public let defaultValue = false
143144
public let key = "FeatureFlag-DisableLazyVStack"
144145
}
146+
147+
public struct PreCacheOnFileOpen: UserDefaultPreferenceKey {
148+
public let defaultValue = true
149+
public let key = "FeatureFlag-PreCacheOnFileOpen"
150+
}
145151
}

Core/Sources/Service/RealtimeSuggestionController.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class RealtimeSuggestionController {
2525
private var activeApplicationMonitorTask: Task<Void, Error>?
2626
private var editorObservationTask: Task<Void, Error>?
2727
private var focusedUIElement: AXUIElement?
28+
private var sourceEditor: AXUIElement?
2829

2930
var isCommentMode: Bool {
3031
UserDefaults.shared.value(for: \.suggestionPresentationMode) == .comment
@@ -108,8 +109,9 @@ public class RealtimeSuggestionController {
108109
let application = AXUIElementCreateApplication(activeXcode.processIdentifier)
109110
guard let focusElement = application.focusedElement else { return }
110111
let focusElementType = focusElement.description
111-
guard focusElementType == "Source Editor" else { return }
112112
focusedUIElement = focusElement
113+
guard focusElementType == "Source Editor" else { return }
114+
sourceEditor = focusElement
113115

114116
editorObservationTask?.cancel()
115117
editorObservationTask = nil
@@ -134,6 +136,28 @@ public class RealtimeSuggestionController {
134136
}
135137
}
136138
}
139+
140+
Task { // Get cache ready for real-time suggestions.
141+
guard UserDefaults.shared.value(for: \.preCacheOnFileOpen) else { return }
142+
guard
143+
let fileURL = try? await Environment.fetchCurrentFileURL(),
144+
let (_, filespace) = try? await Workspace
145+
.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
146+
else { return }
147+
148+
if filespace.uti == nil {
149+
Logger.service.info("Generate cache for file.")
150+
// avoid the command get called twice
151+
filespace.uti = ""
152+
do {
153+
try await Environment.triggerAction("Real-time Suggestions")
154+
} catch {
155+
if filespace.uti?.isEmpty ?? true {
156+
filespace.uti = nil
157+
}
158+
}
159+
}
160+
}
137161
}
138162

139163
func handleHIDEvent(event: CGEvent) async {
@@ -194,7 +218,7 @@ public class RealtimeSuggestionController {
194218
}
195219

196220
// So the editor won't be blocked (after information are cached)!
197-
await PseudoCommandHandler().generateRealtimeSuggestions()
221+
await PseudoCommandHandler().generateRealtimeSuggestions(sourceEditor: sourceEditor)
198222
}
199223
}
200224

Core/Sources/Service/SuggestionCommandHandler/PseudoCommandHandler.swift

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,10 @@ struct PseudoCommandHandler {
3838
))
3939
}
4040

41-
func generateRealtimeSuggestions() async {
42-
// Can't use handler directly if content is not available.
43-
guard let editor = await getEditorContent() else {
44-
try? await Environment.triggerAction("Prefetch Suggestions")
45-
return
46-
}
47-
48-
// If no cache is available, and completion panel is not displayed, try to get it with command.
49-
if editor.uti.isEmpty, await Environment.frontmostXcodeWindowIsEditor() {
50-
try? await Environment.triggerAction("Prefetch Suggestions")
51-
return
52-
}
53-
41+
func generateRealtimeSuggestions(sourceEditor: AXUIElement?) async {
42+
// Can't use handler if content is not available.
43+
guard let editor = await getEditorContent(sourceEditor: sourceEditor) else { return }
44+
5445
// Otherwise, get it from pseudo handler directly.
5546
let mode = UserDefaults.shared.value(for: \.suggestionPresentationMode)
5647
switch mode {
@@ -84,7 +75,8 @@ struct PseudoCommandHandler {
8475
guard let focusElement = application.focusedElement,
8576
focusElement.description == "Source Editor"
8677
else { return }
87-
guard let (content, lines, cursorPosition) = await getFileContent() else {
78+
guard let (content, lines, cursorPosition) = await getFileContent(sourceEditor: nil)
79+
else {
8880
PresentInWindowSuggestionPresenter()
8981
.presentErrorMessage("Unable to get file content.")
9082
return
@@ -103,6 +95,7 @@ struct PseudoCommandHandler {
10395
)) else { return }
10496

10597
let oldPosition = focusElement.selectedTextRange
98+
let oldScrollPosition = focusElement.parent?.verticalScrollBar?.doubleValue
10699

107100
let error = AXUIElementSetAttributeValue(
108101
focusElement,
@@ -138,6 +131,14 @@ struct PseudoCommandHandler {
138131
}
139132
}
140133

134+
if let oldScrollPosition, let scrollBar = focusElement.parent?.verticalScrollBar {
135+
AXUIElementSetAttributeValue(
136+
scrollBar,
137+
kAXValueAttribute as CFString,
138+
oldScrollPosition as CFTypeRef
139+
)
140+
}
141+
141142
} catch {
142143
PresentInWindowSuggestionPresenter().presentError(error)
143144
}
@@ -153,13 +154,13 @@ struct PseudoCommandHandler {
153154
}
154155

155156
private extension PseudoCommandHandler {
156-
func getFileContent() async
157+
func getFileContent(sourceEditor: AXUIElement?) async
157158
-> (content: String, lines: [String], cursorPosition: CursorPosition)?
158159
{
159160
guard let xcode = ActiveApplicationMonitor.activeXcode
160161
?? ActiveApplicationMonitor.latestXcode else { return nil }
161162
let application = AXUIElementCreateApplication(xcode.processIdentifier)
162-
guard let focusElement = application.focusedElement,
163+
guard let focusElement = sourceEditor ?? application.focusedElement,
163164
focusElement.description == "Source Editor"
164165
else { return nil }
165166
guard let selectionRange = focusElement.selectedTextRange else { return nil }
@@ -196,10 +197,10 @@ private extension PseudoCommandHandler {
196197
}
197198

198199
@ServiceActor
199-
func getEditorContent() async -> EditorContent? {
200+
func getEditorContent(sourceEditor: AXUIElement?) async -> EditorContent? {
200201
guard
201202
let filespace = await getFilespace(),
202-
let content = await getFileContent()
203+
let content = await getFileContent(sourceEditor: sourceEditor)
203204
else { return nil }
204205
let uti = filespace.uti ?? ""
205206
let tabSize = filespace.tabSize ?? 4

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ fi
190190
- The first run of the extension will be slow. Be patient.
191191
- The extension uses some dirty tricks to get the file and project/workspace paths. It may fail, it may be incorrect, especially when you have multiple Xcode windows running, and maybe even worse when they are in different displays. I am not sure about that though.
192192
- The suggestions are presented as C-style comments in comment mode, they may break your code if you are editing a JSON file or something.
193-
- When a real-time suggestion request is triggered, there is a chance that it may briefly block the editor. This can occur at most once for each file after each restart of the extension because the extension needs to initiate real-time suggestion by clicking an item from the menu bar. However, once a command has been executed and some information is cached, the extension will be able to trigger real-time suggestion using a different method.
194193

195194
## License
196195

Version.xcconfig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
APP_VERSION = 0.11.1
2-
APP_BUILD = 81
1+
APP_VERSION = 0.11.2
2+
APP_BUILD = 84

appcast.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
<channel>
44
<title>Copilot for Xcode</title>
55

6+
<item>
7+
<title>0.11.2</title>
8+
<pubDate>Wed, 12 Apr 2023 12:23:29 +0800</pubDate>
9+
<sparkle:version>84</sparkle:version>
10+
<sparkle:shortVersionString>0.11.2</sparkle:shortVersionString>
11+
<sparkle:minimumSystemVersion>12.0</sparkle:minimumSystemVersion>
12+
<sparkle:releaseNotesLink>
13+
https://github.com/intitni/CopilotForXcode/releases/tag/0.11.2
14+
</sparkle:releaseNotesLink>
15+
<enclosure url="https://github.com/intitni/CopilotForXcode/releases/download/0.11.2/Copilot.for.Xcode.app.zip" length="19043705" type="application/octet-stream" sparkle:edSignature="oIfDoluwAzNJmZnN6zZtjRt7uyIMpS738kBNnOvsM8Ic06UQSW5v2bYyIRKnLJAfAygu7DM2/z+alzrUWvW6DA=="/>
16+
</item>
17+
618
<item>
719
<title>0.11.0</title>
820
<pubDate>Sat, 08 Apr 2023 23:32:22 +0800</pubDate>

0 commit comments

Comments
 (0)