Skip to content

Commit 3cb5a6b

Browse files
committed
Merge tag '0.19.2' into develop
2 parents 05ed533 + fc7403e commit 3cb5a6b

File tree

4 files changed

+107
-42
lines changed

4 files changed

+107
-42
lines changed

Core/Sources/Service/RealtimeSuggestionController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public class RealtimeSuggestionController {
198198
}
199199
if Task.isCancelled { return }
200200

201-
Logger.service.info("Prefetch suggestions.")
201+
// Logger.service.info("Prefetch suggestions.")
202202

203203
// So the editor won't be blocked (after information are cached)!
204204
await PseudoCommandHandler().generateRealtimeSuggestions(sourceEditor: sourceEditor)

Core/Sources/XcodeInspector/XcodeInspector.swift

Lines changed: 92 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ public final class XcodeInspector: ObservableObject {
149149
}
150150
}
151151

152+
// MARK: - AppInstanceInspector
153+
152154
public class AppInstanceInspector: ObservableObject {
153155
public let appElement: AXUIElement
154156
public let runningApplication: NSRunningApplication
@@ -160,26 +162,16 @@ public class AppInstanceInspector: ObservableObject {
160162
}
161163
}
162164

163-
public final class XcodeAppInstanceInspector: AppInstanceInspector {
164-
public struct WorkspaceInfo {
165-
public let tabs: Set<String>
166-
167-
public func combined(with info: WorkspaceInfo) -> WorkspaceInfo {
168-
return .init(tabs: info.tabs.union(tabs))
169-
}
170-
}
171-
172-
public enum WorkspaceIdentifier: Hashable {
173-
case url(URL)
174-
case unknown
175-
}
165+
// MARK: - XcodeAppInstanceInspector
176166

167+
public final class XcodeAppInstanceInspector: AppInstanceInspector {
177168
@Published public var focusedWindow: XcodeWindowInspector?
178169
@Published public var documentURL: URL = .init(fileURLWithPath: "/")
179170
@Published public var projectURL: URL = .init(fileURLWithPath: "/")
180-
@Published public var workspaces = [WorkspaceIdentifier: WorkspaceInfo]()
171+
@Published public var workspaces = [WorkspaceIdentifier: Workspace]()
181172
public var realtimeWorkspaces: [WorkspaceIdentifier: WorkspaceInfo] {
182-
Self.fetchWorkspaceInfo(runningApplication)
173+
updateWorkspaceInfo()
174+
return workspaces.mapValues(\.info)
183175
}
184176

185177
@Published public private(set) var completionPanel: AXUIElement?
@@ -284,7 +276,7 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
284276

285277
longRunningTasks.insert(focusedWindowChanged)
286278

287-
workspaces = Self.fetchWorkspaceInfo(runningApplication)
279+
updateWorkspaceInfo()
288280
let updateTabsTask = Task { @MainActor in
289281
let notification = AXNotificationStream(
290282
app: runningApplication,
@@ -294,12 +286,12 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
294286
if #available(macOS 13.0, *) {
295287
for await _ in notification.debounce(for: .seconds(2)) {
296288
try Task.checkCancellation()
297-
workspaces = Self.fetchWorkspaceInfo(runningApplication)
289+
updateWorkspaceInfo()
298290
}
299291
} else {
300292
for await _ in notification {
301293
try Task.checkCancellation()
302-
workspaces = Self.fetchWorkspaceInfo(runningApplication)
294+
updateWorkspaceInfo()
303295
}
304296
}
305297
}
@@ -313,6 +305,8 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
313305
)
314306

315307
for await event in stream {
308+
// We can only observe the creation and closing of the parent
309+
// of the completion panel.
316310
let isCompletionPanel = {
317311
event.element.firstChild { element in
318312
element.identifier == "_XC_COMPLETION_TABLE_"
@@ -336,32 +330,75 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
336330

337331
longRunningTasks.insert(completionPanelTask)
338332
}
333+
}
334+
335+
// MARK: - Workspace Info
336+
337+
extension XcodeAppInstanceInspector {
338+
public enum WorkspaceIdentifier: Hashable {
339+
case url(URL)
340+
case unknown
341+
}
342+
343+
public class Workspace {
344+
public let element: AXUIElement
345+
public var info: WorkspaceInfo
346+
347+
/// When a window is closed, all it's properties will be set to nil.
348+
/// Since we can't get notification for window closing,
349+
/// we will use it to check if the window is closed.
350+
var isValid: Bool {
351+
element.parent != nil
352+
}
353+
354+
init(element: AXUIElement) {
355+
self.element = element
356+
info = .init(tabs: [])
357+
}
358+
}
339359

340-
static func fetchWorkspaceInfo(
360+
public struct WorkspaceInfo {
361+
public let tabs: Set<String>
362+
363+
public func combined(with info: WorkspaceInfo) -> WorkspaceInfo {
364+
return .init(tabs: tabs.union(info.tabs))
365+
}
366+
}
367+
368+
func updateWorkspaceInfo() {
369+
let workspaceInfoInVisibleSpace = Self.fetchVisibleWorkspaces(runningApplication)
370+
workspaces = Self.updateWorkspace(workspaces, with: workspaceInfoInVisibleSpace)
371+
}
372+
373+
/// Use the project path as the workspace identifier.
374+
static func workspaceIdentifier(_ window: AXUIElement) -> WorkspaceIdentifier {
375+
for child in window.children {
376+
if child.description.starts(with: "/"), child.description.count > 1 {
377+
let path = child.description
378+
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
379+
var url = URL(fileURLWithPath: trimmedNewLine)
380+
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
381+
!url.pathExtension.isEmpty
382+
{
383+
url = url.deletingLastPathComponent()
384+
}
385+
return WorkspaceIdentifier.url(url)
386+
}
387+
}
388+
return WorkspaceIdentifier.unknown
389+
}
390+
391+
/// With Accessibility API, we can ONLY get the information of visible windows.
392+
static func fetchVisibleWorkspaces(
341393
_ app: NSRunningApplication
342-
) -> [WorkspaceIdentifier: WorkspaceInfo] {
394+
) -> [WorkspaceIdentifier: Workspace] {
343395
let app = AXUIElementCreateApplication(app.processIdentifier)
344396
let windows = app.windows.filter { $0.identifier == "Xcode.WorkspaceWindow" }
345397

346-
var dict = [WorkspaceIdentifier: WorkspaceInfo]()
398+
var dict = [WorkspaceIdentifier: Workspace]()
347399

348400
for window in windows {
349-
let workspaceIdentifier = {
350-
for child in window.children {
351-
if child.description.starts(with: "/"), child.description.count > 1 {
352-
let path = child.description
353-
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
354-
var url = URL(fileURLWithPath: trimmedNewLine)
355-
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
356-
!url.pathExtension.isEmpty
357-
{
358-
url = url.deletingLastPathComponent()
359-
}
360-
return WorkspaceIdentifier.url(url)
361-
}
362-
}
363-
return WorkspaceIdentifier.unknown
364-
}()
401+
let workspaceIdentifier = workspaceIdentifier(window)
365402

366403
let tabs = {
367404
guard let editArea = window.firstChild(where: { $0.description == "editor area" })
@@ -377,10 +414,26 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
377414
return allTabs
378415
}()
379416

380-
dict[workspaceIdentifier] = .init(tabs: tabs)
417+
let workspace = Workspace(element: window)
418+
workspace.info = .init(tabs: tabs)
419+
dict[workspaceIdentifier] = workspace
381420
}
382-
383421
return dict
384422
}
423+
424+
static func updateWorkspace(
425+
_ old: [WorkspaceIdentifier: Workspace],
426+
with new: [WorkspaceIdentifier: Workspace]
427+
) -> [WorkspaceIdentifier: Workspace] {
428+
var updated = old.filter { $0.value.isValid } // remove closed windows.
429+
for (identifier, workspace) in new {
430+
if let existed = updated[identifier] {
431+
existed.info = workspace.info
432+
} else {
433+
updated[identifier] = workspace
434+
}
435+
}
436+
return updated
437+
}
385438
}
386439

Version.xcconfig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
APP_VERSION = 0.19.1
2-
APP_BUILD = 191
1+
APP_VERSION = 0.19.2
2+
APP_BUILD = 192

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.19.2</title>
8+
<pubDate>Mon, 26 Jun 2023 21:56:13 +0800</pubDate>
9+
<sparkle:version>192</sparkle:version>
10+
<sparkle:shortVersionString>0.19.2</sparkle:shortVersionString>
11+
<sparkle:minimumSystemVersion>12.0</sparkle:minimumSystemVersion>
12+
<sparkle:releaseNotesLink>
13+
https://github.com/intitni/CopilotForXcode/releases/tag/0.19.2
14+
</sparkle:releaseNotesLink>
15+
<enclosure url="https://github.com/intitni/CopilotForXcode/releases/download/0.19.2/Copilot.for.Xcode.app.zip" length="19553870" type="application/octet-stream" sparkle:edSignature="qs7bFWER372/bHPfHzeE29W0fQW3O+fWPiVKJhgOovZqzC5woIYcS2yHi7uzUZhZvwqCunnKiqclN+htnpFoBQ=="/>
16+
</item>
17+
618
<item>
719
<title>0.19.1</title>
820
<pubDate>Sat, 24 Jun 2023 22:34:24 +0800</pubDate>

0 commit comments

Comments
 (0)