Files
i7t5--edmund/Sources/EdmundCore/Editing/EditorTextView+BlockquoteContinuation.swift
wehub-resource-sync 5b99bf6bca
CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:54 +08:00

60 lines
2.7 KiB
Swift

import AppKit
// MARK: - Block-quote / Callout Continuation on Enter
//
// Pressing Return inside a block quote (including a callout) repeats the quote
// prefix on the next line — `> ` — so the quote/callout keeps going, the same
// way list items auto-continue. Pressing Return on an *empty* quote line removes
// the prefix and breaks out of the quote.
extension EditorTextView {
/// Leading indent + one or more `>` levels (each with an optional single
/// space), at the start of a line. Captures the *full* nesting depth so a
/// nested callout/quote line `> > …` continues as `> > `, not `> `.
private static let blockquotePrefixRegex = try! NSRegularExpression(
pattern: #"^[ \t]*(?:>[ \t]?)+"#
)
/// Continues a block quote / callout on Return. Returns true if it handled
/// the newline.
func handleBlockquoteNewline(at location: Int) -> Bool {
let ns = rawSource as NSString
guard location <= ns.length else { return false }
// The line containing the cursor (without its trailing newline).
let lineRange = ns.lineRange(for: NSRange(location: location, length: 0))
var lineEnd = lineRange.upperBound
if lineEnd > lineRange.location, ns.character(at: lineEnd - 1) == 0x0A { lineEnd -= 1 }
let lineStart = lineRange.location
let line = ns.substring(with: NSRange(location: lineStart, length: lineEnd - lineStart))
let lineNS = line as NSString
guard let m = Self.blockquotePrefixRegex.firstMatch(
in: line, range: NSRange(location: 0, length: lineNS.length)) else { return false }
let prefix = lineNS.substring(with: m.range)
let hasContent = m.range.length < lineNS.length
if hasContent {
// Continue the quote/callout at the same depth: newline + same prefix.
insertText("\n" + prefix, replacementRange: NSRange(location: location, length: 0))
} else {
// Empty quote line → step out one nesting level (drop the last `>`).
// At the top level this empties the line, breaking out of the quote.
insertText(Self.reduceQuotePrefix(prefix),
replacementRange: NSRange(location: lineStart, length: lineEnd - lineStart))
}
return true
}
/// Drops the deepest `>` level from a quote prefix: `> > ` → `> `,
/// ` > ` → ` ` (indent kept), `> ` → `` (broken out).
static func reduceQuotePrefix(_ prefix: String) -> String {
let ns = prefix as NSString
let lastGT = ns.range(of: ">", options: .backwards)
guard lastGT.location != NSNotFound else { return "" }
return ns.substring(to: lastGT.location)
}
}