7
7
8
8
import AppKit
9
9
import CoreSpotlight
10
+ import OSLog
10
11
11
12
/// Helper methods for managing the recent projects list and donating list items to CoreSpotlight.
12
13
///
13
14
/// Limits the number of remembered projects to 100 items.
14
15
///
15
16
/// If a UI element needs to listen to changes in this list, listen for the
16
17
/// ``RecentProjectsStore/didUpdateNotification`` notification.
17
- enum RecentProjectsStore {
18
- private static let defaultsKey = " recentProjectPaths "
18
+ class RecentProjectsStore {
19
+ private let logger = Logger ( subsystem: Bundle . main. bundleIdentifier ?? " " , category: " RecentProjectsStore " )
20
+
21
+ /// The default projects store, uses the `UserDefaults.standard` storage location.
22
+ static let shared = RecentProjectsStore ( )
23
+
24
+ private static let projectsdDefaultsKey = " recentProjectPaths "
19
25
static let didUpdateNotification = Notification . Name ( " RecentProjectsStore.didUpdate " )
20
26
21
- static func recentProjectPaths( ) -> [ String ] {
22
- UserDefaults . standard. array ( forKey: defaultsKey) as? [ String ] ?? [ ]
27
+ /// The storage location for recent projects
28
+ let defaults : UserDefaults
29
+
30
+ #if DEBUG
31
+ /// Create a new store with a `UserDefaults` storage location.
32
+ init ( defaults: UserDefaults = UserDefaults . standard) {
33
+ self . defaults = defaults
34
+ }
35
+ #else
36
+ /// Create a new store with a `UserDefaults` storage location.
37
+ private init ( defaults: UserDefaults = UserDefaults . standard) {
38
+ self . defaults = defaults
23
39
}
40
+ #endif
24
41
25
- static func recentProjectURLs( ) -> [ URL ] {
26
- recentProjectPaths ( ) . map { URL ( filePath: $0) }
42
+ /// Gets the recent paths array from `UserDefaults`.
43
+ private func recentPaths( ) -> [ String ] {
44
+ defaults. array ( forKey: Self . projectsdDefaultsKey) as? [ String ] ?? [ ]
27
45
}
28
46
29
- private static func setPaths( _ paths: [ String ] ) {
30
- var paths = paths
31
- // Remove duplicates
32
- var foundPaths = Set < String > ( )
33
- for (idx, path) in paths. enumerated ( ) . reversed ( ) {
34
- if foundPaths. contains ( path) {
35
- paths. remove ( at: idx)
36
- } else {
37
- foundPaths. insert ( path)
38
- }
39
- }
47
+ /// Gets all recent paths from `UserDefaults` as an array of `URL`s. Includes both **projects** and
48
+ /// **single files**.
49
+ /// To filter for either projects or single files, use ``recentProjectURLs()`` or ``recentFileURLs``, respectively.
50
+ func recentURLs( ) -> [ URL ] {
51
+ recentPaths ( ) . map { URL ( filePath: $0) }
52
+ }
40
53
41
- // Limit list to to 100 items after de-duplication
42
- UserDefaults . standard. setValue ( Array ( paths. prefix ( 100 ) ) , forKey: defaultsKey)
54
+ /// Gets the recent **Project** `URL`s from `UserDefaults`.
55
+ /// To get both single files and projects, use ``recentURLs()``.
56
+ func recentProjectURLs( ) -> [ URL ] {
57
+ recentURLs ( ) . filter { $0. isFolder }
58
+ }
59
+
60
+ /// Gets the recent **Single File** `URL`s from `UserDefaults`.
61
+ /// To get both single files and projects, use ``recentURLs()``.
62
+ func recentFileURLs( ) -> [ URL ] {
63
+ recentURLs ( ) . filter { !$0. isFolder }
64
+ }
65
+
66
+ /// Save a new paths array to defaults. Automatically limits the list to the most recent `100` items, donates
67
+ /// search items to Spotlight, and notifies observers.
68
+ private func setPaths( _ paths: [ String ] ) {
69
+ defaults. setValue ( Array ( paths. prefix ( 100 ) ) , forKey: Self . projectsdDefaultsKey)
43
70
setDocumentControllerRecents ( )
44
71
donateSearchableItems ( )
45
72
NotificationCenter . default. post ( name: Self . didUpdateNotification, object: nil )
@@ -49,41 +76,45 @@ enum RecentProjectsStore {
49
76
/// Moves the path to the front if it was in the list already, or prepends it.
50
77
/// Saves the list to defaults when called.
51
78
/// - Parameter url: The url that was opened. Any url is accepted. File, directory, https.
52
- static func documentOpened( at url: URL ) {
53
- var paths = recentProjectURLs ( )
54
- if let containedIndex = paths. firstIndex ( where: { $0. componentCompare ( url) } ) {
55
- paths. move ( fromOffsets: IndexSet ( integer: containedIndex) , toOffset: 0 )
79
+ func documentOpened( at url: URL ) {
80
+ var projectURLs = recentURLs ( )
81
+
82
+ if let containedIndex = projectURLs. firstIndex ( where: { $0. componentCompare ( url) } ) {
83
+ projectURLs. move ( fromOffsets: IndexSet ( integer: containedIndex) , toOffset: 0 )
56
84
} else {
57
- paths . insert ( url, at: 0 )
85
+ projectURLs . insert ( url, at: 0 )
58
86
}
59
- setPaths ( paths. map { $0. path ( percentEncoded: false ) } )
87
+
88
+ setPaths ( projectURLs. map { $0. path ( percentEncoded: false ) } )
60
89
}
61
90
62
- /// Remove all paths in the set.
91
+ /// Remove all project paths in the set.
63
92
/// - Parameter paths: The paths to remove.
64
93
/// - Returns: The remaining urls in the recent projects list.
65
- static func removeRecentProjects( _ paths: Set < URL > ) -> [ URL ] {
66
- var recentProjectPaths = recentProjectURLs ( )
94
+ func removeRecentProjects( _ paths: Set < URL > ) -> [ URL ] {
95
+ let paths = Set ( paths. map { $0. path ( percentEncoded: false ) } )
96
+ var recentProjectPaths = recentPaths ( )
67
97
recentProjectPaths. removeAll ( where: { paths. contains ( $0) } )
68
- setPaths ( recentProjectPaths. map { $0 . path ( percentEncoded : false ) } )
69
- return recentProjectURLs ( )
98
+ setPaths ( recentProjectPaths)
99
+ return recentURLs ( )
70
100
}
71
101
72
- static func clearList( ) {
102
+ func clearList( ) {
73
103
setPaths ( [ ] )
104
+ NotificationCenter . default. post ( name: Self . didUpdateNotification, object: nil )
74
105
}
75
106
76
107
/// Syncs AppKit's recent documents list with ours, keeping the dock menu and other lists up-to-date.
77
- private static func setDocumentControllerRecents( ) {
108
+ private func setDocumentControllerRecents( ) {
78
109
CodeEditDocumentController . shared. clearRecentDocuments ( nil )
79
- for path in recentProjectURLs ( ) . prefix ( 10 ) {
110
+ for path in recentURLs ( ) . prefix ( 10 ) {
80
111
CodeEditDocumentController . shared. noteNewRecentDocumentURL ( path)
81
112
}
82
113
}
83
114
84
115
/// Donates all recent URLs to Core Search, making them searchable in Spotlight
85
- private static func donateSearchableItems( ) {
86
- let searchableItems = recentProjectURLs ( ) . map { entity in
116
+ private func donateSearchableItems( ) {
117
+ let searchableItems = recentURLs ( ) . map { entity in
87
118
let attributeSet = CSSearchableItemAttributeSet ( contentType: . content)
88
119
attributeSet. title = entity. lastPathComponent
89
120
attributeSet. relatedUniqueIdentifier = entity. path ( )
@@ -93,9 +124,9 @@ enum RecentProjectsStore {
93
124
attributeSet: attributeSet
94
125
)
95
126
}
96
- CSSearchableIndex . default ( ) . indexSearchableItems ( searchableItems) { error in
127
+ CSSearchableIndex . default ( ) . indexSearchableItems ( searchableItems) { [ weak self ] error in
97
128
if let error = error {
98
- print ( error)
129
+ self ? . logger . debug ( " Failed to donate recent projects, error: \( error , privacy : . auto ) " )
99
130
}
100
131
}
101
132
}
0 commit comments