diff --git a/XRPC.xcworkspace/xcuserdata/llsc12.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/XRPC.xcworkspace/xcuserdata/llsc12.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 083d04e..7923f8b 100644 --- a/XRPC.xcworkspace/xcuserdata/llsc12.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/XRPC.xcworkspace/xcuserdata/llsc12.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,4 +3,22 @@ uuid = "56FD24EC-8B81-45C4-A37F-8A8B759FA931" type = "0" version = "2.0"> + + + + + + diff --git a/XRPC/AXScrape.swift b/XRPC/AXScrape.swift index ab79f53..a292d97 100644 --- a/XRPC/AXScrape.swift +++ b/XRPC/AXScrape.swift @@ -52,18 +52,53 @@ class AXScrape: ObservableObject { let doc: URL? = docFilePath == nil ? nil : URL(fileURLWithPath: docFilePath!.replacingOccurrences(of: "file://", with: "")) let currentSessionDate: Date? = { - switch presenceState { - case .working(let xcodeState): + if case .working(let xcodeState) = presenceState { return xcodeState.sessionDate - default: return nil } + return nil }() + var warnings: Int, errors: Int, issues: Int + warnings = 0 + errors = 0 + issues = 0 + + // we need to get the ui elements for the header bar of xcode since thats where the warns and errors are displayed + if + let focusedWindow, + let sourceEditor = filterElements(of: focusedWindow, filter: { + (try? $0.role()) ?? .unknown == .textArea && + (try? $0.attribute(.description) ?? "") == "Source Editor" + }).first + + { + // editor shows errors, runtime warnings and warnings etc in "Line Annotation" id'd elements + let annotations = filterElements(of: sourceEditor, filter: { + (try? $0.role()) ?? .unknown == .button && + (try? $0.attribute(.identifier)) == "Line Annotation" + }) + + for annotation in annotations { + issues += 1 + let line = ((try? annotation.attribute(.description) ?? "") ?? "") + if line.starts(with: "Warning") && line.contains("Runtime Issue") { + warnings += 1 + } else if line.starts(with: "Warning") { + warnings += 1 + } else if line.starts(with: "Error") { + errors += 1 + } + } + } + let xcState = XcodeState( workspace: workspace, editorFile: doc, isEditingFile: isEditing, - sessionDate: currentSessionDate ?? xcodeProcess?.launchDate ?? .now /// preserve xcode last date or make new date, used for timings + sessionDate: currentSessionDate ?? xcodeProcess?.launchDate ?? .now, /// preserve xcode last date or make new date, used for timings + errors: errors, + warnings: warnings, + totalIssues: issues ) @@ -86,6 +121,10 @@ struct XcodeState: Equatable { var sessionDate: Date? + var errors: Int + var warnings: Int + var totalIssues: Int + /// Is true if xcode has no file open (sitting in xcodeproj or xcworkspace) var isIdle: Bool { editorFile?.lastPathComponent.contains("xcodeproj") ?? true || editorFile?.lastPathComponent.contains("xcworkspace") ?? true @@ -115,3 +154,73 @@ fileprivate extension String { self.components(separatedBy: string).count - 1 } } + + +func traverseHierarchy(of element: UIElement, level: Int = 0) { + let indent = String(repeating: " ", count: level) + + // Log the current (parent) element's details + do { + let role = try element.attribute(.role) as String? ?? "Unknown" + let title = try element.attribute(.title) as String? ?? "No Title" + let help = try element.attribute(.help) as String? ?? "No Help" + let desc = try element.attribute(.description) as String? ?? "No Description" + let vald = try element.attribute(.valueDescription) as String? ?? "No Value Description" + + // print("\(indent)- [\(role) name] \(title)") + // print("\(indent)- [\(role) help] \(help)") + // print("\(indent)- [\(role) desc] \(desc)") + // print("\(indent)- [\(role) vald] \(vald)") + print("\(indent)- [\(role)] \(try! element.getMultipleAttributes(element.attributes()).map {($0.key.rawValue, $0.value)})") + print("") + +// Any as String + } catch { + print("\(indent)- Error accessing attributes for element: \(error)") + } + + // Get the children of the current element + if let children = try? element.children() { + for child in children { + // Recursive call to traverse and log children + traverseHierarchy(of: child, level: level + 1) + } + } +} + +extension UIElement { + func children() throws -> [UIElement] { + let a: [AXUIElement] = (try attribute(.children)) ?? [AXUIElement]() + return a.map { UIElement($0) } + } +} + +/// Recursively traverses the accessibility hierarchy and applies a closure to filter elements. +/// +/// - Parameters: +/// - element: The root `UIElement` to start the traversal from. +/// - level: The current depth in the hierarchy (used for indentation/debugging purposes). +/// - filter: A closure that takes a `UIElement` and returns a `Bool`. If the closure returns `true`, the element is considered a match. +/// - Returns: A list of `UIElement` objects that match the filter condition. +func filterElements( + of element: UIElement, + level: Int = 0, + filter: (UIElement) -> Bool +) -> [UIElement] { + var matchingElements = [UIElement]() + + // Check if the current element matches the filter + if filter(element) { + matchingElements.append(element) + } + + // Recursively traverse the children of the element + if let children = try? element.children() { + for child in children { + let childMatches = filterElements(of: child, level: level + 1, filter: filter) + matchingElements.append(contentsOf: childMatches) + } + } + + return matchingElements +} diff --git a/XRPC/AppDelegate.swift b/XRPC/AppDelegate.swift index cf393a1..ce1164e 100644 --- a/XRPC/AppDelegate.swift +++ b/XRPC/AppDelegate.swift @@ -59,11 +59,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { // self.window?.standardWindowButton(.closeButton)?.isHidden = true self.window?.standardWindowButton(.miniaturizeButton)?.isHidden = true self.window?.standardWindowButton(.zoomButton)?.isHidden = true - let titlebar = self.window?.standardWindowButton(.closeButton)?.superview +// let titlebar = self.window?.standardWindowButton(.closeButton)?.superview // self.window?.titlebarAppearsTransparent = true self.window?.makeKeyAndOrderFront(self) SetupVM.shared.setupWindowClose = { self.window?.close(); self.window = nil} } + } class KillOnCloseViewController: NSViewController { diff --git a/XRPC/Base.lproj/Main.storyboard b/XRPC/Base.lproj/Main.storyboard index 25f575d..5349c69 100644 --- a/XRPC/Base.lproj/Main.storyboard +++ b/XRPC/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - + - + + diff --git a/XRPC/RPC.swift b/XRPC/RPC.swift index 1fae885..0934f1e 100644 --- a/XRPC/RPC.swift +++ b/XRPC/RPC.swift @@ -78,14 +78,24 @@ class RPC: ObservableObject, SwordRPCDelegate { presence.details = "In \(ws)" } + // if issues != 0 + var issuesString = "" + if xcodeState.totalIssues != 0 { + if xcodeState.errors != 0 { + issuesString = "⛔️\(xcodeState.errors) " + } else if xcodeState.warnings != 0 { + issuesString = "⚠️\(xcodeState.totalIssues - xcodeState.errors) " + } + } + if xcodeState.isIdle { presence.state = "Idling in Xcode" } else { if let filename = xcodeState.fileName { if xcodeState.isEditingFile { - presence.state = "Editing \(filename)" + presence.state = "Editing \(filename) \(issuesString)" } else { - presence.state = "Viewing \(filename)" + presence.state = "Viewing \(filename) \(issuesString)" } } }