@@ -52,13 +52,20 @@ class MonitoringManager {
52
52
let entityUnwrapped = entity. 0
53
53
else { return nil }
54
54
55
- return HeartbeatData (
55
+ let project = project ( for: app, activeWindow)
56
+ var language = language ( for: app, activeWindow)
57
+ if project != nil && language == nil {
58
+ language = " <<LAST_LANGUAGE>> "
59
+ }
60
+
61
+ let heartbeat = HeartbeatData (
56
62
entity: entityUnwrapped,
57
63
entityType: entity. 1 ,
58
- project: project ( for : app , activeWindow ) ,
59
- language: language ( for : app ) ,
60
- category: category ( for: app)
64
+ project: project,
65
+ language: language,
66
+ category: category ( for: app, activeWindow )
61
67
)
68
+ return heartbeat
62
69
}
63
70
64
71
static var isMonitoringBrowsing : Bool {
@@ -95,9 +102,11 @@ class MonitoringManager {
95
102
}
96
103
97
104
static func set( monitoringState: MonitoringState , for bundleId: String ) {
98
- let allApps = allMonitoredApps
99
- if !allApps. contains ( bundleId) {
100
- UserDefaults . standard. set ( allApps + [ bundleId] , forKey: monitoringKey)
105
+ if monitoringState == . on {
106
+ UserDefaults . standard. set ( Array ( Set ( allMonitoredApps + [ bundleId] ) ) , forKey: monitoringKey)
107
+ } else {
108
+ let apps = allMonitoredApps. filter { $0 != bundleId }
109
+ UserDefaults . standard. set ( apps, forKey: monitoringKey)
101
110
}
102
111
UserDefaults . standard. synchronize ( )
103
112
}
@@ -251,8 +260,11 @@ class MonitoringManager {
251
260
}
252
261
}
253
262
254
- static func category( for app: NSRunningApplication ) -> Category ? {
255
- guard let monitoredApp = app. monitoredApp else { return . coding }
263
+ static func category( for app: NSRunningApplication , _ element: AXUIElement ) -> Category {
264
+ guard let monitoredApp = app. monitoredApp else {
265
+ guard let url = currentBrowserUrl ( for: app, element) else { return . coding }
266
+ return category ( from: url)
267
+ }
256
268
257
269
switch monitoredApp {
258
270
case . adobeaftereffect:
@@ -325,6 +337,29 @@ class MonitoringManager {
325
337
}
326
338
// swiftlint:enable cyclomatic_complexity
327
339
340
+ static func category( from url: String ) -> Category {
341
+ let patterns = [
342
+ " github.com/[^/]+/[^/]+/pull/.*$ " ,
343
+ " gitlab.com/[^/]+/[^/]+/[^/]+/merge_requests/.*$ " ,
344
+ " bitbucket.org/[^/]+/[^/]+/pull-requests/.*$ " ,
345
+ ]
346
+
347
+ for pattern in patterns {
348
+ do {
349
+ let regex = try NSRegularExpression ( pattern: pattern)
350
+ let nsrange = NSRange ( url. startIndex..< url. endIndex, in: url)
351
+ if regex. firstMatch ( in: url, options: [ ] , range: nsrange) != nil {
352
+ return . codereviewing
353
+ }
354
+ } catch {
355
+ Logging . default. log ( " Regex error: \( error) " )
356
+ continue
357
+ }
358
+ }
359
+
360
+ return . coding
361
+ }
362
+
328
363
static func project( for app: NSRunningApplication , _ element: AXUIElement ) -> String ? {
329
364
guard let monitoredApp = app. monitoredApp else {
330
365
guard let url = currentBrowserUrl ( for: app, element) else { return nil }
@@ -346,6 +381,7 @@ class MonitoringManager {
346
381
static func project( from url: String ) -> String ? {
347
382
let patterns = [
348
383
" github.com/([^/]+/[^/]+)/?.*$ " ,
384
+ " gitlab.com/([^/]+/[^/]+)/?.*$ " ,
349
385
" bitbucket.org/([^/]+/[^/]+)/?.*$ " ,
350
386
" app.circleci.com/.*/?(github|bitbucket|gitlab)/([^/]+/[^/]+)/?.*$ " ,
351
387
" app.travis-ci.com/(github|bitbucket|gitlab)/([^/]+/[^/]+)/?.*$ " ,
@@ -376,12 +412,41 @@ class MonitoringManager {
376
412
return nil
377
413
}
378
414
379
- static func language( for app: NSRunningApplication ) -> String ? {
415
+ static func language( for app: NSRunningApplication , _ element : AXUIElement ) -> String ? {
380
416
guard let monitoredApp = app. monitoredApp else { return nil }
381
417
382
418
switch monitoredApp {
383
419
case . canva:
384
420
return " Image (svg) "
421
+ case . chrome:
422
+ do {
423
+ guard let url = currentBrowserUrl ( for: app, element) else { return nil }
424
+
425
+ let regex = try NSRegularExpression ( pattern: " github.com/[^/]+/[^/]+/?$ " )
426
+ let nsrange = NSRange ( url. startIndex..< url. endIndex, in: url)
427
+ if regex. firstMatch ( in: url, options: [ ] , range: nsrange) != nil {
428
+ let languages = element. firstDescendantWhere { $0. role == " AXStaticText " && $0. value == " Languages " }
429
+ guard let languages = languages else { return nil }
430
+
431
+ guard let wrapper = languages. parent? . parent else { return nil }
432
+
433
+ let langList = wrapper. firstDescendantWhere { $0. role == " AXList " }
434
+ guard let langList = langList else { return nil }
435
+
436
+ let link = langList. firstDescendantWhere { $0. role == " AXLink " }
437
+ guard let link = link else { return nil }
438
+
439
+ let lang = link. firstDescendantWhere { $0. role == " AXStaticText " }
440
+ guard let lang = lang else { return nil }
441
+
442
+ return lang. value
443
+ }
444
+
445
+ return nil
446
+ } catch {
447
+ Logging . default. log ( " Error parsing language from browser: \( error) " )
448
+ return nil
449
+ }
385
450
case . figma:
386
451
return " Image (svg) "
387
452
case . postman:
0 commit comments