fix toolbar persistence, menu pruning, and tabbed window frame handling

This commit is contained in:
Darin Kelkhoff committed 2026-07-13 14:45:56 -05:00
1 parent 1abc51eb25
commit baac7e71a8
5 files changed
+123 -22

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# Think different
## Here's to the crazy ones.
1. The misfits
2. The rebels
3. The troublemakers
The round pegs in the square holes. The ones who see things differently. They're not fond of rules.
And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. About the only thing you can't do is ignore them. Because they change things.
***They push the human race forward***.
And while some may see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do.
[Think different](http://www.apple.com)
+44 -4
View File
@@ -342,24 +342,34 @@ final class DocumentToolbarController: NSObject, ObservableObject, NSToolbarDele
self.setActiveThemeName = setActiveThemeName
self.showMissingEditor = showMissingEditor
if window.toolbar?.identifier != .documentToolbar || window.toolbar?.delegate !== self {
Self.removeLegacySavedToolbarConfigurations()
if window.toolbar?.identifier != .documentToolbar {
let toolbar = NSToolbar(identifier: .documentToolbar)
toolbar.delegate = self
toolbar.allowsUserCustomization = true
toolbar.autosavesConfiguration = true
toolbar.displayMode = NSToolbar.DisplayMode.default
window.toolbar = toolbar
} else if window.toolbar?.delegate !== self {
window.toolbar?.delegate = self
}
updateVisibleItems()
}
private static func removeLegacySavedToolbarConfigurations() {
let defaults = UserDefaults.standard
for identifier in legacyToolbarIdentifiers {
defaults.removeObject(forKey: "NSToolbar Configuration \(identifier)")
}
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.toc, .tocDepth, .markdownSource, .zoom, .theme, .externalEditor, .space]
return [.toc, .tocDepth, .markdownSource, .zoom, .theme, .externalEditor, .fixedSpace]
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.toc, .tocDepth, .space, .markdownSource, .zoom, .theme, .externalEditor]
return [.toc, .tocDepth, .fixedSpace, .markdownSource, .zoom, .theme, .externalEditor]
}
func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
@@ -391,11 +401,25 @@ final class DocumentToolbarController: NSObject, ObservableObject, NSToolbarDele
return makeThemeItem()
case .externalEditor:
return makeExternalEditorItem()
case .fixedSpace:
return makeFixedSpaceItem()
default:
return nil
}
}
private func makeFixedSpaceItem() -> NSToolbarItem {
let item = NSToolbarItem(itemIdentifier: .fixedSpace)
item.label = "Space"
item.paletteLabel = "Space"
let spacer = NSView()
spacer.translatesAutoresizingMaskIntoConstraints = false
spacer.widthAnchor.constraint(equalToConstant: 16).isActive = true
spacer.heightAnchor.constraint(equalToConstant: 1).isActive = true
item.view = spacer
return item
}
private func makeTOCItem() -> NSToolbarItem {
let item = NSToolbarItem(itemIdentifier: .toc)
item.label = "TOC"
@@ -730,16 +754,22 @@ final class DocumentToolbarController: NSObject, ObservableObject, NSToolbarDele
case .toc:
item.image = NSImage(systemSymbolName: tocVisible ? "list.bullet.circle.fill" : "list.bullet.circle", accessibilityDescription: "TOC")
item.toolTip = tocVisible ? "Hide Table of Contents" : "Show Table of Contents"
item.target = self
item.action = #selector(toggleTOC)
item.menuFormRepresentation = toolbarMenuItem(title: "TOC", action: #selector(toggleTOC), state: tocVisible ? .on : .off)
case .tocDepth:
if let popup = item.view as? NSPopUpButton {
popup.selectItem(at: max(0, min(5, tocDepth - 1)))
popup.isEnabled = tocVisible
popup.target = self
popup.action = #selector(tocDepthChanged(_:))
}
item.menuFormRepresentation = tocDepthMenuItem()
case .markdownSource:
item.image = NSImage(systemSymbolName: rawVisible ? "doc.plaintext.fill" : "doc.plaintext", accessibilityDescription: "Markdown Source")
item.toolTip = rawVisible ? "Hide Markdown Source" : "Show Markdown Source"
item.target = self
item.action = #selector(toggleMarkdownSource)
item.menuFormRepresentation = toolbarMenuItem(title: "Markdown Source", action: #selector(toggleMarkdownSource), state: rawVisible ? .on : .off)
case .zoom:
item.view = zoomControlView()
@@ -751,6 +781,8 @@ final class DocumentToolbarController: NSObject, ObservableObject, NSToolbarDele
popup.addItem(withTitle: theme.name)
}
popup.selectItem(withTitle: activeThemeName)
popup.target = self
popup.action = #selector(themeChanged(_:))
}
item.menuFormRepresentation = themeMenuItem()
case .externalEditor:
@@ -778,12 +810,20 @@ private extension NSToolbarItem.Identifier {
static let zoom = NSToolbarItem.Identifier("zoom")
static let theme = NSToolbarItem.Identifier("theme")
static let externalEditor = NSToolbarItem.Identifier("external-editor")
static let fixedSpace = NSToolbarItem.Identifier("document-fixed-space")
}
private extension NSToolbar.Identifier {
static let documentToolbar = NSToolbar.Identifier("document-toolbar-v3")
static let documentToolbar = NSToolbar.Identifier("document-toolbar-v5")
}
private let legacyToolbarIdentifiers = [
"document-toolbar",
"document-toolbar-v2",
"document-toolbar-v3",
"document-toolbar-v4"
]
private final class ZoomToolbarControlView: NSView {
private let control: NSSegmentedControl
+24 -7
View File
@@ -25,25 +25,42 @@ enum FileMenuPruner {
}
}
final class FileMenuPrunerApplicationDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
final class FileMenuPrunerApplicationDelegate: NSObject, NSApplicationDelegate {
private var menuObserver: NSObjectProtocol?
func applicationDidFinishLaunching(_ notification: Notification) {
installMenuObserver()
DispatchQueue.main.async {
self.configureFileMenu()
}
}
func applicationWillUpdate(_ notification: Notification) {
configureFileMenu()
deinit {
if let menuObserver {
NotificationCenter.default.removeObserver(menuObserver)
}
}
func menuWillOpen(_ menu: NSMenu) {
FileMenuPruner.removeEditingItems(from: menu)
ExternalEditorFileMenuController.shared.update(in: menu)
private func installMenuObserver() {
guard menuObserver == nil else { return }
menuObserver = NotificationCenter.default.addObserver(
forName: NSMenu.didBeginTrackingNotification,
object: nil,
queue: .main
) { notification in
guard let menu = notification.object as? NSMenu,
menu === FileMenuPruner.fileMenu(in: NSApp.mainMenu)
else { return }
self.updateFileMenu(menu)
}
}
private func configureFileMenu() {
guard let fileMenu = FileMenuPruner.fileMenu(in: NSApp.mainMenu) else { return }
fileMenu.delegate = self
updateFileMenu(fileMenu)
}
private func updateFileMenu(_ fileMenu: NSMenu) {
FileMenuPruner.removeEditingItems(from: fileMenu)
ExternalEditorFileMenuController.shared.update(in: fileMenu)
}
+14 -4
View File
@@ -169,6 +169,10 @@ struct DocumentWindowConfigurator: NSViewRepresentable {
return NSSize(width: width, height: 800)
}
static func shouldApplyDocumentFrame(isTabbedWindow: Bool) -> Bool {
!isTabbedWindow
}
static func autosaveName(for fileURL: URL) -> String {
var allowedCharacters = CharacterSet.alphanumerics
allowedCharacters.insert(charactersIn: "-_.")
@@ -200,15 +204,21 @@ struct DocumentWindowConfigurator: NSViewRepresentable {
let autosaveName = Self.autosaveName(for: fileURL)
guard context.coordinator.needsConfiguration(for: window, autosaveName: autosaveName) else { return }
let restoredFrame = window.setFrameUsingName(autosaveName)
if !restoredFrame {
window.setContentSize(Self.defaultDocumentWindowSize(tocVisible: defaultTocVisible, rawVisible: defaultRawVisible))
window.center()
if Self.shouldApplyDocumentFrame(isTabbedWindow: Self.isTabbedWindow(window)) {
let restoredFrame = window.setFrameUsingName(autosaveName)
if !restoredFrame {
window.setContentSize(Self.defaultDocumentWindowSize(tocVisible: defaultTocVisible, rawVisible: defaultRawVisible))
window.center()
}
}
window.setFrameAutosaveName(autosaveName)
context.coordinator.configureSavingFrame(for: window, autosaveName: autosaveName)
}
private static func isTabbedWindow(_ window: NSWindow) -> Bool {
window.tabbedWindows?.contains { $0 !== window } == true
}
class Coordinator {
private weak var configuredWindow: NSWindow?
private var configuredAutosaveName: String?
@@ -79,7 +79,12 @@ final class TemplateScrollbarTests: XCTestCase {
XCTAssertTrue(appSource.contains("FileMenuPrunerApplicationDelegate"))
let prunerSource = try loadSourceFile("markdownViewr/FileMenuPruner.swift")
XCTAssertTrue(prunerSource.contains("func menuWillOpen"))
XCTAssertTrue(prunerSource.contains("NSMenu.didBeginTrackingNotification"))
XCTAssertFalse(prunerSource.contains("NSMenuDelegate"))
XCTAssertFalse(prunerSource.contains(".delegate = self"))
XCTAssertFalse(prunerSource.contains("func menuNeedsUpdate"))
XCTAssertFalse(prunerSource.contains("func applicationWillUpdate"))
XCTAssertFalse(prunerSource.contains("func menuWillOpen"))
}
func testExternalEditorCommandIsSeparatedBelowOpenRecent() throws {
@@ -265,6 +270,11 @@ final class TemplateScrollbarTests: XCTestCase {
XCTAssertEqual(DocumentWindowConfigurator.defaultDocumentWindowSize(tocVisible: true, rawVisible: true), NSSize(width: 1500, height: 800))
}
func testDocumentWindowFrameConfigurationSkipsTabbedWindows() {
XCTAssertTrue(DocumentWindowConfigurator.shouldApplyDocumentFrame(isTabbedWindow: false))
XCTAssertFalse(DocumentWindowConfigurator.shouldApplyDocumentFrame(isTabbedWindow: true))
}
func testInitialSourceWidthUsesHalfAvailableWindowWidth() {
XCTAssertEqual(
DocumentViewLayout.initialRawWidth(windowWidth: 1200, tocVisible: false, tocWidth: 300),
@@ -384,19 +394,26 @@ final class TemplateScrollbarTests: XCTestCase {
XCTAssertTrue(contentSource.contains("toolbar.allowsUserCustomization = true"))
XCTAssertTrue(contentSource.contains("toolbar.autosavesConfiguration = true"))
XCTAssertTrue(contentSource.contains("toolbar.displayMode = NSToolbar.DisplayMode.default"))
XCTAssertTrue(contentSource.contains(#"NSToolbar.Identifier("document-toolbar-v3")"#))
XCTAssertFalse(contentSource.contains(#"NSToolbar.Identifier("document-toolbar-v2")"#))
XCTAssertTrue(contentSource.contains(#"NSToolbar.Identifier("document-toolbar-v5")"#))
XCTAssertTrue(contentSource.contains("removeLegacySavedToolbarConfigurations()"))
XCTAssertTrue(contentSource.contains(#""NSToolbar Configuration \(identifier)""#))
XCTAssertTrue(contentSource.contains(#""document-toolbar-v4""#))
XCTAssertFalse(contentSource.contains(#"NSToolbar.Identifier("document-toolbar-v4")"#))
XCTAssertTrue(contentSource.contains("} else if window.toolbar?.delegate !== self {\n window.toolbar?.delegate = self\n }"))
XCTAssertTrue(contentSource.contains("item.target = self"))
XCTAssertTrue(contentSource.contains("toolbarAllowedItemIdentifiers"))
XCTAssertTrue(contentSource.contains("toolbarDefaultItemIdentifiers"))
}
func testAppKitToolbarCustomizationIncludesRepeatableSpaceItems() throws {
func testDocumentToolbarIncludesFixedSpaceItem() throws {
let contentSource = try loadSourceFile("markdownViewr/ContentView.swift")
XCTAssertTrue(contentSource.contains(".space"))
XCTAssertFalse(contentSource.contains(".space"))
XCTAssertFalse(contentSource.contains(".flexibleSpace"))
XCTAssertTrue(contentSource.contains("return [.toc, .tocDepth, .markdownSource, .zoom, .theme, .externalEditor, .space]"))
XCTAssertTrue(contentSource.contains("return [.toc, .tocDepth, .space, .markdownSource, .zoom, .theme, .externalEditor]"))
XCTAssertTrue(contentSource.contains("return [.toc, .tocDepth, .markdownSource, .zoom, .theme, .externalEditor, .fixedSpace]"))
XCTAssertTrue(contentSource.contains("return [.toc, .tocDepth, .fixedSpace, .markdownSource, .zoom, .theme, .externalEditor]"))
XCTAssertTrue(contentSource.contains("case .fixedSpace:\n return makeFixedSpaceItem()"))
XCTAssertTrue(contentSource.contains(#"NSToolbarItem.Identifier("document-fixed-space")"#))
}
private func loadTemplate() throws -> String {