From 3d81e7ae11d2c906ef44a603e99ce4e61d52bbff Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Sun, 21 Jul 2024 00:27:58 +0200 Subject: [PATCH] Support for Zed --- CONTRIBUTING.md | 23 +++++- README.md | 8 -- .../Extensions/AXUIElementExtension.swift | 18 ----- WakaTime/Watchers/MonitoredApp.swift | 79 +++++++++++++++---- 4 files changed, 81 insertions(+), 47 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b671bd9..cc36704 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,17 @@ # Contributing -To contribute to this project please carefully read this document. - ## Setup -`macos-wakatime` is written in [Swift](https://www.swift.org/). +This project depends on the [xcodegen](https://github.com/yonaskolb/XcodeGen?tab=readme-ov-file#installing) command line tool. + +```bash +git clone git@github.com:wakatime/macos-wakatime.git +cd macos-wakatime +xcodegen +``` + +Then open the `WakaTime.xcodeproj` in [Xcode 15.2](https://developer.apple.com/services-account/download?path=/Developer_Tools/Xcode_15.2/Xcode_15.2.xip). +Currently there’s a bug in new Swift compiler versions, so the largest Xcode version working with this app is 15.2. ## Branches @@ -23,7 +30,15 @@ To fix linter warning(s), run `swiftlint --fix`. ## Branching Stratgegy -Please follow our guideline for branch names [here](https://github.com/wakatime/semver-action#branch-names). Branches off the pattern won't be accepted. +We require specific branch name prefixes for PRs: + +- `^major/.+` - `major` +- `^feature/.+` - `minor` +- `^bugfix/.+` - `patch` +- `^docs?/.+` - `build` +- `^misc/.+` - `build` + +More info at [wakatime/semver-action](https://github.com/wakatime/semver-action#branch-names). ## Pull Requests diff --git a/README.md b/README.md index 4b0665b..1e63a7a 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,6 @@ Before requesting support for a new app, first check the [list of supported apps Pull requests and issues are welcome! See [Contributing][contributing] for more details. -The main thing to know is we require specific branch name prefixes for PRs: - -- `^major/.+` - `major` -- `^feature/.+` - `minor` -- `^bugfix/.+` - `patch` -- `^docs?/.+` - `build` -- `^misc/.+` - `build` - Many thanks to all [contributors][authors]! Made with :heart: by the WakaTime Team. diff --git a/WakaTime/Extensions/AXUIElementExtension.swift b/WakaTime/Extensions/AXUIElementExtension.swift index a85feaf..fe00ae2 100644 --- a/WakaTime/Extensions/AXUIElementExtension.swift +++ b/WakaTime/Extensions/AXUIElementExtension.swift @@ -369,24 +369,6 @@ extension AXUIElement { "Value: \"\(ellipsedValue)\"" ) } - - func extractPrefix(_ str: String?, separator: String, minCount: Int? = nil, fullTitle: Bool = false) -> String? { - guard let str = str else { return nil } - - let parts = str.components(separatedBy: separator) - - if let minCount = minCount, minCount > 0, parts.count < minCount { - return nil - } - - if !parts.isEmpty && parts[0].trimmingCharacters(in: .whitespacesAndNewlines) != "" { - if fullTitle { - return str.trimmingCharacters(in: .whitespacesAndNewlines) - } - return parts[0].trimmingCharacters(in: .whitespacesAndNewlines) - } - return nil - } } enum AXUIElementNotification { diff --git a/WakaTime/Watchers/MonitoredApp.swift b/WakaTime/Watchers/MonitoredApp.swift index f5962d9..fcf34bf 100644 --- a/WakaTime/Watchers/MonitoredApp.swift +++ b/WakaTime/Watchers/MonitoredApp.swift @@ -23,6 +23,7 @@ enum MonitoredApp: String, CaseIterable { case wecom = "com.tencent.WeWorkMac" case whatsapp = "net.whatsapp.WhatsApp" case xcode = "com.apple.dt.Xcode" + case zed = "dev.zed.Zed" case zoom = "us.zoom.xos" init?(from bundleId: String) { @@ -65,6 +66,7 @@ enum MonitoredApp: String, CaseIterable { MonitoredApp.tableplus.rawValue, MonitoredApp.xcode.rawValue, MonitoredApp.zoom.rawValue, + MonitoredApp.zed.rawValue, ] // list apps which we aren't yet able to track, so they're hidden from the Monitored Apps menu @@ -118,13 +120,20 @@ enum MonitoredApp: String, CaseIterable { fatalError("\(rawValue) should never use window title") case .zoom: return .meeting + case .zed: + return .coding } } func project(for element: AXUIElement) -> String? { // TODO: detect repo from GitHub Desktop Client if possible - guard let url = currentBrowserUrl(for: element) else { return nil } - return project(from: url) + switch self { + case .zed: + return extractSuffix(element.rawTitle, separator: " — ") + default: + guard let url = currentBrowserUrl(for: element) else { return nil } + return project(from: url) + } } private func project(from url: String) -> String? { @@ -234,7 +243,7 @@ enum MonitoredApp: String, CaseIterable { // ICTK2MacTextView let textAreaElement = element.firstDescendantWhere { $0.role == kAXTextAreaRole } if let value = textAreaElement?.value { - let title = element.extractPrefix(value, separator: "\n") + let title = extractPrefix(value, separator: "\n") return title } return nil @@ -256,7 +265,7 @@ enum MonitoredApp: String, CaseIterable { fatalError("\(self.rawValue) should never use window title as entity") case .figma: guard - let title = element.extractPrefix(element.rawTitle, separator: " – "), + let title = extractPrefix(element.rawTitle, separator: " – "), title != "Figma", title != "Drafts" else { return nil } @@ -264,50 +273,86 @@ enum MonitoredApp: String, CaseIterable { case .firefox: fatalError("\(self.rawValue) should never use window title as entity") case .github: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .imessage: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .iterm2: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .linear: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .notes: fatalError("\(self.rawValue) should never use window title as entity") case .notion: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .postman: guard - let title = element.extractPrefix(element.rawTitle, separator: " - ", fullTitle: true), + let title = extractPrefix(element.rawTitle, separator: " - ", fullTitle: true), title != "Postman" else { return nil } return title case .slack: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .safari: fatalError("\(self.rawValue) should never use window title as entity") case .safaripreview: fatalError("\(self.rawValue) should never use window title as entity") case .tableplus: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .terminal: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .warp: guard - let title = element.extractPrefix(element.rawTitle, separator: " - "), + let title = extractPrefix(element.rawTitle, separator: " - "), title != "Warp" else { return nil } return title case .wecom: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .whatsapp: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") case .xcode: fatalError("\(self.rawValue) should never use window title as entity") case .zoom: - return element.extractPrefix(element.rawTitle, separator: " - ") + return extractPrefix(element.rawTitle, separator: " - ") + case .zed: + return extractPrefix(element.rawTitle, separator: " — ") } } + private func extractPrefix(_ str: String?, separator: String, minCount: Int? = nil, fullTitle: Bool = false) -> String? { + guard let str = str else { return nil } + + let parts = str.components(separatedBy: separator) + guard !parts.isEmpty else { return nil } + guard let item = parts.first else { return nil } + + if let minCount = minCount, minCount > 0, parts.count < minCount { + return nil + } + + if item.trimmingCharacters(in: .whitespacesAndNewlines) != "" { + if fullTitle { + return str.trimmingCharacters(in: .whitespacesAndNewlines) + } + return item.trimmingCharacters(in: .whitespacesAndNewlines) + } + return nil + } + + private func extractSuffix(_ str: String?, separator: String) -> String? { + guard let str = str else { return nil } + + let parts = str.components(separatedBy: separator) + guard !parts.isEmpty else { return nil } + guard let item = parts.last else { return nil } + + if item.trimmingCharacters(in: .whitespacesAndNewlines) != "" { + return item.trimmingCharacters(in: .whitespacesAndNewlines) + } + + return nil + } + private func domainFromUrl(_ url: String) -> String? { guard let host = URL(stringWithoutScheme: url)?.host else { return nil } let domain = host.replacingOccurrences(of: "^www.", with: "", options: .regularExpression)