keyboard shortcuts, menu items, and header zoom scaling

This commit is contained in:
jess 2026-04-06 15:38:11 -07:00
parent bb3cc40b51
commit 9209619473
3 changed files with 166 additions and 19 deletions

View File

@ -3,12 +3,22 @@ import Combine
import SwiftUI
import UniformTypeIdentifiers
class WindowController {
let window: NSWindow
let appState: AppState
init(window: NSWindow, appState: AppState) {
self.window = window
self.appState = appState
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var appState: AppState!
private var titleCancellable: AnyCancellable?
private var titleBarView: TitleBarView?
private var focusTitleObserver: NSObjectProtocol?
private var windowControllers: [WindowController] = []
func applicationDidFinishLaunching(_ notification: Notification) {
_ = ConfigManager.shared
@ -99,9 +109,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let item = NSMenuItem()
let menu = NSMenu(title: "File")
let newItem = NSMenuItem(title: "New Note", action: #selector(newNote), keyEquivalent: "n")
newItem.target = self
menu.addItem(newItem)
let newWindowItem = NSMenuItem(title: "New Window", action: #selector(newWindow), keyEquivalent: "n")
newWindowItem.target = self
menu.addItem(newWindowItem)
let newNoteItem = NSMenuItem(title: "New Note", action: #selector(newNote), keyEquivalent: "N")
newNoteItem.keyEquivalentModifierMask = [.command, .shift]
newNoteItem.target = self
menu.addItem(newNoteItem)
let openItem = NSMenuItem(title: "Open...", action: #selector(openNote), keyEquivalent: "o")
openItem.target = self
@ -117,6 +132,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
saveAsItem.target = self
menu.addItem(saveAsItem)
menu.addItem(.separator())
let openStorageItem = NSMenuItem(title: "Open Storage Directory", action: #selector(openStorageDirectory), keyEquivalent: "")
openStorageItem.target = self
menu.addItem(openStorageItem)
item.submenu = menu
return item
}
@ -133,6 +154,26 @@ class AppDelegate: NSObject, NSApplicationDelegate {
menu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")
menu.addItem(.separator())
let boldItem = NSMenuItem(title: "Bold", action: #selector(boldSelection), keyEquivalent: "b")
boldItem.target = self
menu.addItem(boldItem)
let italicItem = NSMenuItem(title: "Italic", action: #selector(italicizeSelection), keyEquivalent: "i")
italicItem.target = self
menu.addItem(italicItem)
menu.addItem(.separator())
let tableItem = NSMenuItem(title: "Insert Table", action: #selector(insertTable), keyEquivalent: "t")
tableItem.target = self
menu.addItem(tableItem)
let evalItem = NSMenuItem(title: "Smart Eval", action: #selector(smartEval), keyEquivalent: "e")
evalItem.target = self
menu.addItem(evalItem)
menu.addItem(.separator())
let findItem = NSMenuItem(title: "Find...", action: #selector(NSTextView.performFindPanelAction(_:)), keyEquivalent: "f")
findItem.tag = Int(NSTextFinder.Action.showFindInterface.rawValue)
menu.addItem(findItem)
@ -190,6 +231,51 @@ class AppDelegate: NSObject, NSApplicationDelegate {
appState.newNote()
}
@objc private func newWindow() {
let state = AppState()
let contentView = ContentView(state: state)
let hostingView = NSHostingView(rootView: contentView)
let win = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 1200, height: 800),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered,
defer: false
)
win.titlebarAppearsTransparent = true
win.titleVisibility = .hidden
win.backgroundColor = Theme.current.base
win.title = "Swiftly"
win.contentView = hostingView
win.center()
win.makeKeyAndOrderFront(nil)
let controller = WindowController(window: win, appState: state)
windowControllers.append(controller)
}
@objc private func openStorageDirectory() {
let dir = ConfigManager.shared.autoSaveDirectory
let url = URL(fileURLWithPath: dir, isDirectory: true)
NSWorkspace.shared.open(url)
}
@objc private func boldSelection() {
NotificationCenter.default.post(name: .boldSelection, object: nil)
}
@objc private func italicizeSelection() {
NotificationCenter.default.post(name: .italicizeSelection, object: nil)
}
@objc private func insertTable() {
NotificationCenter.default.post(name: .insertTable, object: nil)
}
@objc private func smartEval() {
NotificationCenter.default.post(name: .smartEval, object: nil)
}
@objc private func openNote() {
let panel = NSOpenPanel()
panel.allowedContentTypes = Self.supportedContentTypes

View File

@ -31,4 +31,8 @@ extension Notification.Name {
static let focusEditor = Notification.Name("focusEditor")
static let focusTitle = Notification.Name("focusTitle")
static let formatDocument = Notification.Name("formatDocument")
static let insertTable = Notification.Name("insertTable")
static let boldSelection = Notification.Name("boldSelection")
static let italicizeSelection = Notification.Name("italicizeSelection")
static let smartEval = Notification.Name("smartEval")
}

View File

@ -1262,26 +1262,25 @@ struct EditorTextView: NSViewRepresentable {
private var isUpdatingImages = false
private var isUpdatingTables = false
private var embeddedTableViews: [MarkdownTableView] = []
private var focusObserver: NSObjectProtocol?
private var settingsObserver: NSObjectProtocol?
private var observers: [NSObjectProtocol] = []
init(_ parent: EditorTextView) {
self.parent = parent
super.init()
focusObserver = NotificationCenter.default.addObserver(
observers.append(NotificationCenter.default.addObserver(
forName: .focusEditor, object: nil, queue: .main
) { [weak self] _ in
guard let tv = self?.textView else { return }
tv.window?.makeFirstResponder(tv)
tv.setSelectedRange(NSRange(location: 0, length: 0))
}
NotificationCenter.default.addObserver(
})
observers.append(NotificationCenter.default.addObserver(
forName: .formatDocument, object: nil, queue: .main
) { [weak self] _ in
self?.formatCurrentDocument()
}
settingsObserver = NotificationCenter.default.addObserver(
})
observers.append(NotificationCenter.default.addObserver(
forName: .settingsChanged, object: nil, queue: .main
) { [weak self] _ in
guard let tv = self?.textView, let ts = tv.textStorage else { return }
@ -1297,14 +1296,31 @@ struct EditorTextView: NSViewRepresentable {
applySyntaxHighlighting(to: ts, format: parent.fileFormat)
ts.endEditing()
tv.needsDisplay = true
}
})
observers.append(NotificationCenter.default.addObserver(
forName: .boldSelection, object: nil, queue: .main
) { [weak self] _ in
self?.wrapSelection(with: "**")
})
observers.append(NotificationCenter.default.addObserver(
forName: .italicizeSelection, object: nil, queue: .main
) { [weak self] _ in
self?.wrapSelection(with: "*")
})
observers.append(NotificationCenter.default.addObserver(
forName: .insertTable, object: nil, queue: .main
) { [weak self] _ in
self?.insertBlankTable()
})
observers.append(NotificationCenter.default.addObserver(
forName: .smartEval, object: nil, queue: .main
) { [weak self] _ in
self?.performSmartEval()
})
}
deinit {
if let obs = focusObserver {
NotificationCenter.default.removeObserver(obs)
}
if let obs = settingsObserver {
for obs in observers {
NotificationCenter.default.removeObserver(obs)
}
}
@ -1497,6 +1513,47 @@ struct EditorTextView: NSViewRepresentable {
textView.insertText("\n" + indent, replacementRange: textView.selectedRange())
}
private func wrapSelection(with wrapper: String) {
guard let tv = textView else { return }
let sel = tv.selectedRange()
guard sel.length > 0 else { return }
let str = tv.string as NSString
let selected = str.substring(with: sel)
let wrapped = wrapper + selected + wrapper
tv.insertText(wrapped, replacementRange: sel)
tv.setSelectedRange(NSRange(location: sel.location + wrapper.count, length: selected.count))
}
private func insertBlankTable() {
guard let tv = textView else { return }
let table = "| Column 1 | Column 2 | Column 3 |\n| --- | --- | --- |\n| | | |\n"
tv.insertText(table, replacementRange: tv.selectedRange())
}
private func performSmartEval() {
guard let tv = textView else { return }
let str = tv.string as NSString
let cursor = tv.selectedRange().location
let lineRange = str.lineRange(for: NSRange(location: cursor, length: 0))
let line = str.substring(with: lineRange).trimmingCharacters(in: .newlines)
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("let ") {
if let eqIdx = trimmed.firstIndex(of: "="), eqIdx > trimmed.startIndex {
let afterLet = trimmed[trimmed.index(trimmed.startIndex, offsetBy: 4)..<eqIdx]
.trimmingCharacters(in: .whitespaces)
let insertion = "\n/= \(afterLet)\n"
let endOfLine = NSMaxRange(lineRange)
tv.insertText(insertion, replacementRange: NSRange(location: endOfLine, length: 0))
}
} else if !trimmed.isEmpty {
let lineStart = lineRange.location
let whitespacePrefix = line.prefix(while: { $0 == " " || $0 == "\t" })
let insertLoc = lineStart + whitespacePrefix.count
tv.insertText("/= ", replacementRange: NSRange(location: insertLoc, length: 0))
}
}
func textView(_ textView: NSTextView, clickedOnLink link: Any, at charIndex: Int) -> Bool {
var urlString: String?
if let url = link as? URL {
@ -1946,7 +2003,7 @@ private func highlightMarkdownLine(_ trimmed: String, line: String, lineRange: N
let contentStart = hashRange.location + hashRange.length
let contentRange = NSRange(location: contentStart, length: NSMaxRange(lineRange) - contentStart)
if contentRange.length > 0 {
let h3Font = NSFont.systemFont(ofSize: 15, weight: .bold)
let h3Font = NSFont.systemFont(ofSize: round(baseFont.pointSize * 1.15), weight: .bold)
textStorage.addAttribute(.font, value: h3Font, range: contentRange)
textStorage.addAttribute(.foregroundColor, value: palette.text, range: contentRange)
}
@ -1961,7 +2018,7 @@ private func highlightMarkdownLine(_ trimmed: String, line: String, lineRange: N
let contentStart = hashRange.location + hashRange.length
let contentRange = NSRange(location: contentStart, length: NSMaxRange(lineRange) - contentStart)
if contentRange.length > 0 {
let h2Font = NSFont.systemFont(ofSize: 18, weight: .bold)
let h2Font = NSFont.systemFont(ofSize: round(baseFont.pointSize * 1.38), weight: .bold)
textStorage.addAttribute(.font, value: h2Font, range: contentRange)
textStorage.addAttribute(.foregroundColor, value: palette.text, range: contentRange)
}
@ -1976,7 +2033,7 @@ private func highlightMarkdownLine(_ trimmed: String, line: String, lineRange: N
let contentStart = hashRange.location + hashRange.length
let contentRange = NSRange(location: contentStart, length: NSMaxRange(lineRange) - contentStart)
if contentRange.length > 0 {
let h1Font = NSFont.systemFont(ofSize: 22, weight: .bold)
let h1Font = NSFont.systemFont(ofSize: round(baseFont.pointSize * 1.69), weight: .bold)
textStorage.addAttribute(.font, value: h1Font, range: contentRange)
textStorage.addAttribute(.foregroundColor, value: palette.text, range: contentRange)
}