Skip to content

Separated files and folders in the "Open Recent" sub-menu #2039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 66 additions & 13 deletions CodeEdit/Features/Welcome/Model/RecentProjectsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@ import CoreSpotlight
/// If a UI element needs to listen to changes in this list, listen for the
/// ``RecentProjectsStore/didUpdateNotification`` notification.
enum RecentProjectsStore {
private static let defaultsKey = "recentProjectPaths"
private static let projectsdDefaultsKey = "recentProjectPaths"
private static let fileDefaultsKey = "recentFilePaths"
static let didUpdateNotification = Notification.Name("RecentProjectsStore.didUpdate")

static func recentProjectPaths() -> [String] {
UserDefaults.standard.array(forKey: defaultsKey) as? [String] ?? []
UserDefaults.standard.array(forKey: projectsdDefaultsKey) as? [String] ?? []
}

static func recentProjectURLs() -> [URL] {
recentProjectPaths().map { URL(filePath: $0) }
return recentProjectPaths().map { URL(filePath: $0) }
}

private static func setPaths(_ paths: [String]) {
static func recentFilePaths() -> [String] {
UserDefaults.standard.array(forKey: fileDefaultsKey) as? [String] ?? []
}

static func recentFileURLs() -> [URL] {
return recentFilePaths().map { URL(filePath: $0) }
}

private static func setProjectPaths(_ paths: [String]) {
var paths = paths
// Remove duplicates
var foundPaths = Set<String>()
Expand All @@ -39,7 +48,25 @@ enum RecentProjectsStore {
}

// Limit list to to 100 items after de-duplication
UserDefaults.standard.setValue(Array(paths.prefix(100)), forKey: defaultsKey)
UserDefaults.standard.setValue(Array(paths.prefix(100)), forKey: projectsdDefaultsKey)
setDocumentControllerRecents()
donateSearchableItems()
NotificationCenter.default.post(name: Self.didUpdateNotification, object: nil)
}
private static func setFilePaths(_ paths: [String]) {
var paths = paths
// Remove duplicates
var foundPaths = Set<String>()
for (idx, path) in paths.enumerated().reversed() {
if foundPaths.contains(path) {
paths.remove(at: idx)
} else {
foundPaths.insert(path)
}
}

// Limit list to to 100 items after de-duplication
UserDefaults.standard.setValue(Array(paths.prefix(100)), forKey: fileDefaultsKey )
setDocumentControllerRecents()
donateSearchableItems()
NotificationCenter.default.post(name: Self.didUpdateNotification, object: nil)
Expand All @@ -50,27 +77,53 @@ enum RecentProjectsStore {
/// Saves the list to defaults when called.
/// - Parameter url: The url that was opened. Any url is accepted. File, directory, https.
static func documentOpened(at url: URL) {
var paths = recentProjectURLs()
if let containedIndex = paths.firstIndex(where: { $0.componentCompare(url) }) {
paths.move(fromOffsets: IndexSet(integer: containedIndex), toOffset: 0)
var projPaths = recentProjectURLs()
var filePaths = recentFileURLs()

let urlToString = url.absoluteString

// if file portion of local URL has "/" at the end then it is a folder , files and folders go in two separate lists

if urlToString.hasSuffix("/") {
if let containedIndex = projPaths.firstIndex(where: { $0.componentCompare(url) }) {
projPaths.move(fromOffsets: IndexSet(integer: containedIndex), toOffset: 0)
} else {
projPaths.insert(url, at: 0)
}
setProjectPaths(projPaths.map { $0.path(percentEncoded: false) })
} else {
paths.insert(url, at: 0)
if let containedIndex = filePaths.firstIndex(where: { $0.componentCompare(url) }) {
filePaths.move(fromOffsets: IndexSet(integer: containedIndex), toOffset: 0)
} else {
filePaths.insert(url, at: 0)
}
setFilePaths(filePaths.map { $0.path(percentEncoded: false) })
}
setPaths(paths.map { $0.path(percentEncoded: false) })
}

/// Remove all paths in the set.
/// Remove all project paths in the set.
/// - Parameter paths: The paths to remove.
/// - Returns: The remaining urls in the recent projects list.
static func removeRecentProjects(_ paths: Set<URL>) -> [URL] {
var recentProjectPaths = recentProjectURLs()
recentProjectPaths.removeAll(where: { paths.contains($0) })
setPaths(recentProjectPaths.map { $0.path(percentEncoded: false) })
setProjectPaths(recentProjectPaths.map { $0.path(percentEncoded: false) })
return recentProjectURLs()
}
/// Remove all folder paths in the set.
/// - Parameter paths: The paths to remove.
/// - Returns: The remaining urls in the recent projects list.
static func removeRecentFiles(_ paths: Set<URL>) -> [URL] {
var recentFilePaths = recentFileURLs()
recentFilePaths.removeAll(where: { paths.contains($0) })
setFilePaths(recentFilePaths.map { $0.path(percentEncoded: false) })
return recentFileURLs()
}

static func clearList() {
setPaths([])
setProjectPaths([])
setFilePaths([])
NotificationCenter.default.post(name: Self.didUpdateNotification, object: nil)
}

/// Syncs AppKit's recent documents list with ours, keeping the dock menu and other lists up-to-date.
Expand Down
71 changes: 59 additions & 12 deletions CodeEdit/Features/WindowCommands/Utils/RecentProjectsMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,26 @@ class RecentProjectsMenu: NSObject {
func makeMenu() -> NSMenu {
let menu = NSMenu(title: NSLocalizedString("Open Recent", comment: "Open Recent menu title"))

let paths = RecentProjectsStore.recentProjectURLs().prefix(10)
projectItems(menu: menu)
menu.addItem(NSMenuItem.separator())
fileItems(menu: menu)

menu.addItem(NSMenuItem.separator())
let clearMenuItem = NSMenuItem(
title: NSLocalizedString("Clear Menu", comment: "Recent project menu clear button"),
action: #selector(clearMenuItemClicked(_:)),
keyEquivalent: ""
)
clearMenuItem.target = self
menu.addItem(clearMenuItem)

return menu
}

for projectPath in paths {
private func projectItems( menu: NSMenu) {
let projectPaths = RecentProjectsStore.recentProjectURLs().prefix(10)

for projectPath in projectPaths {
let icon = NSWorkspace.shared.icon(forFile: projectPath.path())
icon.size = NSSize(width: 16, height: 16)
let alternateTitle = alternateTitle(for: projectPath)
Expand All @@ -27,7 +44,7 @@ class RecentProjectsMenu: NSObject {
primaryItem.image = icon
primaryItem.representedObject = projectPath

let containsDuplicate = paths.contains { url in
let containsDuplicate = projectPaths.contains { url in
url != projectPath && url.lastPathComponent == projectPath.lastPathComponent
}

Expand All @@ -51,18 +68,48 @@ class RecentProjectsMenu: NSObject {
menu.addItem(primaryItem)
menu.addItem(alternateItem)
}
}

menu.addItem(NSMenuItem.separator())
private func fileItems( menu: NSMenu) {
let filePaths = RecentProjectsStore.recentFileURLs().prefix(10)
for filePath in filePaths {
let icon = NSWorkspace.shared.icon(forFile: filePath.path())
icon.size = NSSize(width: 16, height: 16)
let alternateTitle = alternateTitle(for: filePath)

let clearMenuItem = NSMenuItem(
title: NSLocalizedString("Clear Menu", comment: "Recent project menu clear button"),
action: #selector(clearMenuItemClicked(_:)),
keyEquivalent: ""
)
clearMenuItem.target = self
menu.addItem(clearMenuItem)
let primaryItem = NSMenuItem(
title: filePath.lastPathComponent,
action: #selector(recentProjectItemClicked(_:)),
keyEquivalent: ""
)
primaryItem.target = self
primaryItem.image = icon
primaryItem.representedObject = filePath

return menu
let containsDuplicate = filePaths.contains { url in
url != filePath && url.lastPathComponent == filePath.lastPathComponent
}

// If there's a duplicate, add the path.
if containsDuplicate {
primaryItem.attributedTitle = alternateTitle
}

let alternateItem = NSMenuItem(
title: "",
action: #selector(recentProjectItemClicked(_:)),
keyEquivalent: ""
)
alternateItem.attributedTitle = alternateTitle
alternateItem.target = self
alternateItem.image = icon
alternateItem.representedObject = filePath
alternateItem.isAlternate = true
alternateItem.keyEquivalentModifierMask = [.option]

menu.addItem(primaryItem)
menu.addItem(alternateItem)
}
}

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