import AppKit /// Selection-aware Markdown insertion used by both the toolbar and the Format /// menu. Wraps the selection (or inserts a placeholder), keeping the change on /// the text view's own undo stack. enum TextMarkup { static func wrap(_ textView: NSTextView, prefix: String, suffix: String, placeholder: String) { let range = textView.selectedRange() let ns = textView.string as NSString let selected = ns.substring(with: range) let body = selected.isEmpty ? placeholder : selected let replacement = prefix + body + suffix guard textView.shouldChangeText(in: range, replacementString: replacement) else { return } textView.replaceCharacters(in: range, with: replacement) textView.didChangeText() // Re-select the body so the user can keep typing or toggle again. let bodyStart = range.location + (prefix as NSString).length textView.setSelectedRange(NSRange(location: bodyStart, length: (body as NSString).length)) } /// Prefix every line touched by the selection (headings, lists, quotes). static func linePrefix(_ textView: NSTextView, prefix: String) { let ns = textView.string as NSString let lineRange = ns.lineRange(for: textView.selectedRange()) let block = ns.substring(with: lineRange) let prefixed = block .components(separatedBy: "\n") .enumerated() .map { idx, line in // Don't add a stray prefix to a trailing empty line from the split. (line.isEmpty && idx > 0) ? line : prefix + line } .joined(separator: "\n") guard textView.shouldChangeText(in: lineRange, replacementString: prefixed) else { return } textView.replaceCharacters(in: lineRange, with: prefixed) textView.didChangeText() textView.setSelectedRange(NSRange(location: lineRange.location, length: (prefixed as NSString).length)) } }