@@ -14,47 +14,50 @@ import CoreSpotlight
1414///
1515/// If a UI element needs to listen to changes in this list, listen for the
1616/// ``RecentProjectsStore/didUpdateNotification`` notification.
17- enum RecentProjectsStore {
17+ class RecentProjectsStore {
18+ /// The default projects store, uses the `UserDefaults.standard` storage location.
19+ static let `default` = RecentProjectsStore ( )
20+
1821 private static let projectsdDefaultsKey = " recentProjectPaths "
19- private static let fileDefaultsKey = " recentFilePaths "
2022 static let didUpdateNotification = Notification . Name ( " RecentProjectsStore.didUpdate " )
2123
22- static func recentProjectPaths( ) -> [ String ] {
23- UserDefaults . standard. array ( forKey: projectsdDefaultsKey) as? [ String ] ?? [ ]
24- }
24+ /// The storage location for recent projeects
25+ let defaults : UserDefaults
2526
26- static func recentProjectURLs( ) -> [ URL ] {
27- return recentProjectPaths ( ) . map { URL ( filePath: $0) }
27+ /// Create a new store with a `UserDefaults` storage location.
28+ init ( defaults: UserDefaults = UserDefaults . standard) {
29+ self . defaults = defaults
2830 }
2931
30- static func recentFilePaths( ) -> [ String ] {
31- UserDefaults . standard. array ( forKey: fileDefaultsKey) as? [ String ] ?? [ ]
32+ /// Gets the recent paths array from `UserDefaults`.
33+ private func recentPaths( ) -> [ String ] {
34+ defaults. array ( forKey: Self . projectsdDefaultsKey) as? [ String ] ?? [ ]
3235 }
3336
34- static func recentFileURLs( ) -> [ URL ] {
35- return recentFilePaths ( ) . map { URL ( filePath: $0) }
37+ /// Gets all recent paths from `UserDefaults` as an array of `URL`s. Includes both **projects** and
38+ /// **single files**.
39+ /// To filter for either projects or single files, use ``recentProjectURLs()`` or ``recentFileURLs``, respectively.
40+ func recentURLs( ) -> [ URL ] {
41+ recentPaths ( ) . map { URL ( filePath: $0) }
3642 }
3743
38- private static func setProjectPaths( _ paths: [ String ] ) {
39- var paths = paths
40- // Remove duplicates
41- var foundPaths = Set < String > ( )
42- for (idx, path) in paths. enumerated ( ) . reversed ( ) {
43- if foundPaths. contains ( path) {
44- paths. remove ( at: idx)
45- } else {
46- foundPaths. insert ( path)
47- }
48- }
44+ /// Gets the recent **Project** `URL`s from `UserDefaults`.
45+ /// To get both single files and projects, use ``recentURLs()``.
46+ func recentProjectURLs( ) -> [ URL ] {
47+ recentURLs ( ) . filter { $0. isFolder }
48+ }
4949
50- // Limit list to to 100 items after de-duplication
51- UserDefaults . standard. setValue ( Array ( paths. prefix ( 100 ) ) , forKey: projectsdDefaultsKey)
52- setDocumentControllerRecents ( )
53- donateSearchableItems ( )
54- NotificationCenter . default. post ( name: Self . didUpdateNotification, object: nil )
50+ /// Gets the recent **Single File** `URL`s from `UserDefaults`.
51+ /// To get both single files and projects, use ``recentURLs()``.
52+ func recentFileURLs( ) -> [ URL ] {
53+ recentURLs ( ) . filter { !$0. isFolder }
5554 }
56- private static func setFilePaths( _ paths: [ String ] ) {
55+
56+ /// Save a new paths array to defaults. Automatically limits the list to the most recent `100` items, donates
57+ /// search items to Spotlight, and notifies observers.
58+ private func setPaths( _ paths: [ String ] ) {
5759 var paths = paths
60+
5861 // Remove duplicates
5962 var foundPaths = Set < String > ( )
6063 for (idx, path) in paths. enumerated ( ) . reversed ( ) {
@@ -66,7 +69,7 @@ enum RecentProjectsStore {
6669 }
6770
6871 // Limit list to to 100 items after de-duplication
69- UserDefaults . standard . setValue ( Array ( paths. prefix ( 100 ) ) , forKey: fileDefaultsKey )
72+ defaults . setValue ( Array ( paths. prefix ( 100 ) ) , forKey: Self . projectsdDefaultsKey )
7073 setDocumentControllerRecents ( )
7174 donateSearchableItems ( )
7275 NotificationCenter . default. post ( name: Self . didUpdateNotification, object: nil )
@@ -76,66 +79,43 @@ enum RecentProjectsStore {
7679 /// Moves the path to the front if it was in the list already, or prepends it.
7780 /// Saves the list to defaults when called.
7881 /// - Parameter url: The url that was opened. Any url is accepted. File, directory, https.
79- static func documentOpened( at url: URL ) {
80- var projPaths = recentProjectURLs ( )
81- var filePaths = recentFileURLs ( )
82+ func documentOpened( at url: URL ) {
83+ var projectURLs = recentURLs ( )
8284
83- let urlToString = url. absoluteString
84-
85- // if file portion of local URL has "/" at the end then it is a folder , files and folders go in two separate lists
86-
87- if urlToString. hasSuffix ( " / " ) {
88- if let containedIndex = projPaths. firstIndex ( where: { $0. componentCompare ( url) } ) {
89- projPaths. move ( fromOffsets: IndexSet ( integer: containedIndex) , toOffset: 0 )
90- } else {
91- projPaths. insert ( url, at: 0 )
92- }
93- setProjectPaths ( projPaths. map { $0. path ( percentEncoded: false ) } )
85+ if let containedIndex = projectURLs. firstIndex ( where: { $0. componentCompare ( url) } ) {
86+ projectURLs. move ( fromOffsets: IndexSet ( integer: containedIndex) , toOffset: 0 )
9487 } else {
95- if let containedIndex = filePaths. firstIndex ( where: { $0. componentCompare ( url) } ) {
96- filePaths. move ( fromOffsets: IndexSet ( integer: containedIndex) , toOffset: 0 )
97- } else {
98- filePaths. insert ( url, at: 0 )
99- }
100- setFilePaths ( filePaths. map { $0. path ( percentEncoded: false ) } )
88+ projectURLs. insert ( url, at: 0 )
10189 }
90+
91+ setPaths ( projectURLs. map { $0. path ( percentEncoded: false ) } )
10292 }
10393
10494 /// Remove all project paths in the set.
10595 /// - Parameter paths: The paths to remove.
10696 /// - Returns: The remaining urls in the recent projects list.
107- static func removeRecentProjects( _ paths: Set < URL > ) -> [ URL ] {
97+ func removeRecentProjects( _ paths: Set < URL > ) -> [ URL ] {
10898 var recentProjectPaths = recentProjectURLs ( )
10999 recentProjectPaths. removeAll ( where: { paths. contains ( $0) } )
110- setProjectPaths ( recentProjectPaths. map { $0. path ( percentEncoded: false ) } )
100+ setPaths ( recentProjectPaths. map { $0. path ( percentEncoded: false ) } )
111101 return recentProjectURLs ( )
112102 }
113- /// Remove all folder paths in the set.
114- /// - Parameter paths: The paths to remove.
115- /// - Returns: The remaining urls in the recent projects list.
116- static func removeRecentFiles( _ paths: Set < URL > ) -> [ URL ] {
117- var recentFilePaths = recentFileURLs ( )
118- recentFilePaths. removeAll ( where: { paths. contains ( $0) } )
119- setFilePaths ( recentFilePaths. map { $0. path ( percentEncoded: false ) } )
120- return recentFileURLs ( )
121- }
122103
123- static func clearList( ) {
124- setProjectPaths ( [ ] )
125- setFilePaths ( [ ] )
104+ func clearList( ) {
105+ setPaths ( [ ] )
126106 NotificationCenter . default. post ( name: Self . didUpdateNotification, object: nil )
127107 }
128108
129109 /// Syncs AppKit's recent documents list with ours, keeping the dock menu and other lists up-to-date.
130- private static func setDocumentControllerRecents( ) {
110+ private func setDocumentControllerRecents( ) {
131111 CodeEditDocumentController . shared. clearRecentDocuments ( nil )
132112 for path in recentProjectURLs ( ) . prefix ( 10 ) {
133113 CodeEditDocumentController . shared. noteNewRecentDocumentURL ( path)
134114 }
135115 }
136116
137117 /// Donates all recent URLs to Core Search, making them searchable in Spotlight
138- private static func donateSearchableItems( ) {
118+ private func donateSearchableItems( ) {
139119 let searchableItems = recentProjectURLs ( ) . map { entity in
140120 let attributeSet = CSSearchableItemAttributeSet ( contentType: . content)
141121 attributeSet. title = entity. lastPathComponent
0 commit comments