textplus/Sources/TextMarkup.swift
monster 09972ccde1 TextPlus: native macOS plaintext editor with sync, scraping, and a freeform board
A no-Xcode AppKit app (swiftc + build.sh):
- Plaintext editor: 5 themes, line numbers, soft wrap, font controls, find,
  live Markdown preview (split mode), selection-aware Markdown toolbar.
- Freeform Board mode: zoomable/pannable canvas of page cards (portrait/landscape,
  US Letter/A4/Legal/Square/Wide/Note), clean boxes, images (paste/drop/panel),
  freehand pen/shapes, screen-grab eyedropper, nested right-click menu, rich
  status bar. The Editor follows the selected page; layout persists to a
  foo.txt.board sidecar; the .txt stays pure plaintext.
- scp-on-save to a Tailscale Mac (key auth, in-flight/quit drain).
- HTML scraping pipeline: custom selector engine + Discogs profile, ingests to
  Postgres (psql) into a marketplace_snapshots schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:08:59 +10:00

40 lines
1.9 KiB
Swift

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))
}
}