Skip to content

Commit 760f866

Browse files
author
atsrus
committed
Issue #1061 Recents menu
1 parent dabc17b commit 760f866

2 files changed

Lines changed: 129 additions & 26 deletions

File tree

CodeEdit/Features/Welcome/Model/RecentProjectsStore.swift

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,27 @@ import CoreSpotlight
1515
/// If a UI element needs to listen to changes in this list, listen for the
1616
/// ``RecentProjectsStore/didUpdateNotification`` notification.
1717
enum RecentProjectsStore {
18-
private static let defaultsKey = "recentProjectPaths"
18+
private static let projectsdDefaultsKey = "recentProjectPaths"
19+
private static let fileDefaultsKey = "recentFilePaths"
1920
static let didUpdateNotification = Notification.Name("RecentProjectsStore.didUpdate")
2021

2122
static func recentProjectPaths() -> [String] {
22-
UserDefaults.standard.array(forKey: defaultsKey) as? [String] ?? []
23+
UserDefaults.standard.array(forKey: projectsdDefaultsKey) as? [String] ?? []
2324
}
2425

2526
static func recentProjectURLs() -> [URL] {
26-
recentProjectPaths().map { URL(filePath: $0) }
27+
return recentProjectPaths().map { URL(filePath: $0) }
2728
}
2829

29-
private static func setPaths(_ paths: [String]) {
30+
static func recentFilePaths() -> [String] {
31+
UserDefaults.standard.array(forKey: fileDefaultsKey) as? [String] ?? []
32+
}
33+
34+
static func recentFileURLs() -> [URL] {
35+
return recentFilePaths().map { URL(filePath: $0) }
36+
}
37+
38+
private static func setProjectPaths(_ paths: [String]) {
3039
var paths = paths
3140
// Remove duplicates
3241
var foundPaths = Set<String>()
@@ -39,7 +48,25 @@ enum RecentProjectsStore {
3948
}
4049

4150
// Limit list to to 100 items after de-duplication
42-
UserDefaults.standard.setValue(Array(paths.prefix(100)), forKey: defaultsKey)
51+
UserDefaults.standard.setValue(Array(paths.prefix(100)), forKey: projectsdDefaultsKey)
52+
setDocumentControllerRecents()
53+
donateSearchableItems()
54+
NotificationCenter.default.post(name: Self.didUpdateNotification, object: nil)
55+
}
56+
private static func setFilePaths(_ paths: [String]) {
57+
var paths = paths
58+
// Remove duplicates
59+
var foundPaths = Set<String>()
60+
for (idx, path) in paths.enumerated().reversed() {
61+
if foundPaths.contains(path) {
62+
paths.remove(at: idx)
63+
} else {
64+
foundPaths.insert(path)
65+
}
66+
}
67+
68+
// Limit list to to 100 items after de-duplication
69+
UserDefaults.standard.setValue(Array(paths.prefix(100)), forKey: fileDefaultsKey )
4370
setDocumentControllerRecents()
4471
donateSearchableItems()
4572
NotificationCenter.default.post(name: Self.didUpdateNotification, object: nil)
@@ -50,29 +77,58 @@ enum RecentProjectsStore {
5077
/// Saves the list to defaults when called.
5178
/// - Parameter url: The url that was opened. Any url is accepted. File, directory, https.
5279
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)
80+
var projPaths = recentProjectURLs()
81+
var filePaths = recentFileURLs()
82+
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) })
5694
} else {
57-
paths.insert(url, at: 0)
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) })
58101
}
59-
setPaths(paths.map { $0.path(percentEncoded: false) })
60102
}
61103

62-
/// Remove all paths in the set.
104+
/// Remove all project paths in the set.
63105
/// - Parameter paths: The paths to remove.
64106
/// - Returns: The remaining urls in the recent projects list.
65107
static func removeRecentProjects(_ paths: Set<URL>) -> [URL] {
66108
var recentProjectPaths = recentProjectURLs()
67109
recentProjectPaths.removeAll(where: { paths.contains($0) })
68-
setPaths(recentProjectPaths.map { $0.path(percentEncoded: false) })
110+
setProjectPaths(recentProjectPaths.map { $0.path(percentEncoded: false) })
69111
return recentProjectURLs()
70112
}
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+
117+
static func removeRecentFiles(_ paths: Set<URL>) -> [URL] {
118+
var recentFilePaths = recentFileURLs()
119+
recentFilePaths.removeAll(where: { paths.contains($0) })
120+
setFilePaths(recentFilePaths.map { $0.path(percentEncoded: false) })
121+
return recentFileURLs()
122+
}
71123

72124
static func clearList() {
73-
setPaths([])
125+
setProjectPaths([])
126+
setFilePaths([])
127+
NotificationCenter.default.post(name: Self.didUpdateNotification, object: nil)
74128
}
75-
129+
// TODO do we need to setdocument controller for Projects AND Files????
130+
// doesn't seem like it clears in teh finder anyway
131+
// ...more testing whcn Cleairng list required.
76132
/// Syncs AppKit's recent documents list with ours, keeping the dock menu and other lists up-to-date.
77133
private static func setDocumentControllerRecents() {
78134
CodeEditDocumentController.shared.clearRecentDocuments(nil)

CodeEdit/Features/WindowCommands/Utils/RecentProjectsMenu.swift

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,26 @@ class RecentProjectsMenu: NSObject {
1111
func makeMenu() -> NSMenu {
1212
let menu = NSMenu(title: NSLocalizedString("Open Recent", comment: "Open Recent menu title"))
1313

14-
let paths = RecentProjectsStore.recentProjectURLs().prefix(10)
14+
projectItems(menu: menu)
15+
menu.addItem(NSMenuItem.separator())
16+
fileItems(menu: menu)
17+
18+
menu.addItem(NSMenuItem.separator())
19+
let clearMenuItem = NSMenuItem(
20+
title: NSLocalizedString("Clear Menu", comment: "Recent project menu clear button"),
21+
action: #selector(clearMenuItemClicked(_:)),
22+
keyEquivalent: ""
23+
)
24+
clearMenuItem.target = self
25+
menu.addItem(clearMenuItem)
26+
27+
return menu
28+
}
1529

16-
for projectPath in paths {
30+
private func projectItems( menu: NSMenu) {
31+
let projectPaths = RecentProjectsStore.recentProjectURLs().prefix(10)
32+
33+
for projectPath in projectPaths {
1734
let icon = NSWorkspace.shared.icon(forFile: projectPath.path())
1835
icon.size = NSSize(width: 16, height: 16)
1936
let alternateTitle = alternateTitle(for: projectPath)
@@ -27,7 +44,7 @@ class RecentProjectsMenu: NSObject {
2744
primaryItem.image = icon
2845
primaryItem.representedObject = projectPath
2946

30-
let containsDuplicate = paths.contains { url in
47+
let containsDuplicate = projectPaths.contains { url in
3148
url != projectPath && url.lastPathComponent == projectPath.lastPathComponent
3249
}
3350

@@ -51,18 +68,48 @@ class RecentProjectsMenu: NSObject {
5168
menu.addItem(primaryItem)
5269
menu.addItem(alternateItem)
5370
}
71+
}
5472

55-
menu.addItem(NSMenuItem.separator())
73+
private func fileItems( menu: NSMenu) {
74+
let filePaths = RecentProjectsStore.recentFileURLs().prefix(10)
75+
for filePath in filePaths {
76+
let icon = NSWorkspace.shared.icon(forFile: filePath.path())
77+
icon.size = NSSize(width: 16, height: 16)
78+
let alternateTitle = alternateTitle(for: filePath)
5679

57-
let clearMenuItem = NSMenuItem(
58-
title: NSLocalizedString("Clear Menu", comment: "Recent project menu clear button"),
59-
action: #selector(clearMenuItemClicked(_:)),
60-
keyEquivalent: ""
61-
)
62-
clearMenuItem.target = self
63-
menu.addItem(clearMenuItem)
80+
let primaryItem = NSMenuItem(
81+
title: filePath.lastPathComponent,
82+
action: #selector(recentProjectItemClicked(_:)),
83+
keyEquivalent: ""
84+
)
85+
primaryItem.target = self
86+
primaryItem.image = icon
87+
primaryItem.representedObject = filePath
6488

65-
return menu
89+
let containsDuplicate = filePaths.contains { url in
90+
url != filePath && url.lastPathComponent == filePath.lastPathComponent
91+
}
92+
93+
// If there's a duplicate, add the path.
94+
if containsDuplicate {
95+
primaryItem.attributedTitle = alternateTitle
96+
}
97+
98+
let alternateItem = NSMenuItem(
99+
title: "",
100+
action: #selector(recentProjectItemClicked(_:)),
101+
keyEquivalent: ""
102+
)
103+
alternateItem.attributedTitle = alternateTitle
104+
alternateItem.target = self
105+
alternateItem.image = icon
106+
alternateItem.representedObject = filePath
107+
alternateItem.isAlternate = true
108+
alternateItem.keyEquivalentModifierMask = [.option]
109+
110+
menu.addItem(primaryItem)
111+
menu.addItem(alternateItem)
112+
}
66113
}
67114

68115
private func alternateTitle(for projectPath: URL) -> NSAttributedString {

0 commit comments

Comments
 (0)