feat: hide rendered markdown formatting markers
This commit is contained in:
@@ -598,14 +598,16 @@ private final class NotesView: NSView {
|
||||
@MainActor
|
||||
enum MarkdownHighlighting {
|
||||
// ponytail: whole-note highlighting; incremental parsing if large notes make typing slow.
|
||||
static let hiddenMarkup = NSAttributedString.Key("Conjure.hiddenMarkup")
|
||||
private static let inlineCode = try! NSRegularExpression(pattern: "(?<![\\\\`])(?<marker>`+)(?<body>[^`\\n]+)\\k<marker>(?!`)")
|
||||
private static let rules: [(NSRegularExpression, [NSAttributedString.Key: Any])] = [
|
||||
(try! NSRegularExpression(pattern: "\\*\\*[^\\n]+?\\*\\*|__[^\\n]+?__"), [.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .bold)]),
|
||||
(try! NSRegularExpression(pattern: "(?<!\\*)\\*(?!\\*)[^*\\n]+\\*(?!\\*)|(?<!_)_(?!_)[^_\\n]+_(?!_)"), [.font: NSFontManager.shared.convert(NotesAppearance.font, toHaveTrait: .italicFontMask)]),
|
||||
(try! NSRegularExpression(pattern: "!?\\[[^\\]\\n]+\\]\\([^\\n)]+\\)|(?m)^\\s*(?:[-+*]|[0-9]+\\.)\\s"), [.foregroundColor: SunsetPalette.accent]),
|
||||
(try! NSRegularExpression(pattern: "~~[^\\n]+?~~"), [.strikethroughStyle: NSUnderlineStyle.single.rawValue]),
|
||||
(try! NSRegularExpression(pattern: "`[^`\\n]+`"), [.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular), .foregroundColor: NSColor.systemGreen]),
|
||||
(try! NSRegularExpression(pattern: "(?<![\\\\*_])(?<marker>\\*{2,3}|_{2,3})(?<body>\\S(?:[^\\n]*?\\S)?)\\k<marker>(?![*_])"), [.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .bold)]),
|
||||
(try! NSRegularExpression(pattern: "(?<![\\\\*_])(?<marker>[*_])(?<body>[^\\s*_](?:[^*_\\n]*?[^\\s*_])?)\\k<marker>(?![*_])"), [.font: NSFontManager.shared.convert(NotesAppearance.font, toHaveTrait: .italicFontMask)]),
|
||||
(try! NSRegularExpression(pattern: "(?<!\\\\)!?\\[(?<body>[^\\]\\n]+)\\]\\([^\\n)]+\\)|(?m)^\\s*(?:[-+*]|[0-9]+\\.)\\s"), [.foregroundColor: SunsetPalette.accent]),
|
||||
(try! NSRegularExpression(pattern: "(?<!\\\\)~~(?<body>\\S(?:[^\\n]*?\\S)?)~~"), [.strikethroughStyle: NSUnderlineStyle.single.rawValue]),
|
||||
(try! NSRegularExpression(pattern: "(?m)^(?:>[ \\t]*)+(?<body>[^\\n]+)"), [:]),
|
||||
]
|
||||
private static let heading = try! NSRegularExpression(pattern: "(?m)^(#{1,6}) .+$")
|
||||
private static let heading = try! NSRegularExpression(pattern: "(?m)^(#{1,6}) (?<body>.+)$")
|
||||
private static let headingFonts = [28.0, 25, 23, 21, 19, 17].map {
|
||||
NSFont.monospacedSystemFont(ofSize: $0, weight: .semibold)
|
||||
}
|
||||
@@ -652,17 +654,40 @@ enum MarkdownHighlighting {
|
||||
if scale < 1 { image.size = NSSize(width: size.width * scale, height: size.height * scale) }
|
||||
}
|
||||
storage.removeAttribute(.strikethroughStyle, range: range)
|
||||
storage.addAttribute(hiddenMarkup, value: false, range: range)
|
||||
storage.addAttributes([.font: NotesAppearance.font, .foregroundColor: SunsetPalette.text,
|
||||
.paragraphStyle: NotesAppearance.paragraph], range: range)
|
||||
let blocks = codeBlocks(in: text)
|
||||
func hideSyntax(_ match: NSTextCheckingResult) {
|
||||
let body = match.range(withName: "body")
|
||||
guard body.location != NSNotFound else { return }
|
||||
if body.location > match.range.location {
|
||||
storage.addAttribute(hiddenMarkup, value: true,
|
||||
range: NSRange(location: match.range.location, length: body.location - match.range.location))
|
||||
}
|
||||
if NSMaxRange(body) < NSMaxRange(match.range) {
|
||||
storage.addAttribute(hiddenMarkup, value: true,
|
||||
range: NSRange(location: NSMaxRange(body), length: NSMaxRange(match.range) - NSMaxRange(body)))
|
||||
}
|
||||
}
|
||||
let codeSpans = inlineCode.matches(in: text, range: range).filter { match in
|
||||
!blocks.contains { NSIntersectionRange($0.range, match.range).length > 0 }
|
||||
}
|
||||
for match in codeSpans {
|
||||
storage.addAttributes([.font: NotesAppearance.font, .foregroundColor: NSColor.systemGreen], range: match.range)
|
||||
hideSyntax(match)
|
||||
}
|
||||
for (expression, attributes) in rules {
|
||||
for match in expression.matches(in: text, range: range) {
|
||||
guard !blocks.contains(where: { NSIntersectionRange($0.range, match.range).length > 0 }) else { continue }
|
||||
guard !blocks.contains(where: { NSIntersectionRange($0.range, match.range).length > 0 }),
|
||||
!codeSpans.contains(where: { NSIntersectionRange($0.range, match.range).length > 0 }) else { continue }
|
||||
storage.addAttributes(attributes, range: match.range)
|
||||
hideSyntax(match)
|
||||
}
|
||||
}
|
||||
for match in heading.matches(in: text, range: range) {
|
||||
guard !blocks.contains(where: { NSIntersectionRange($0.range, match.range).length > 0 }) else { continue }
|
||||
hideSyntax(match)
|
||||
let font = headingFonts[match.range(at: 1).length - 1]
|
||||
storage.enumerateAttribute(.font, in: match.range) { value, run, _ in
|
||||
let traits = (value as? NSFont).map { NSFontManager.shared.traits(of: $0) } ?? []
|
||||
@@ -670,6 +695,15 @@ enum MarkdownHighlighting {
|
||||
}
|
||||
}
|
||||
for block in blocks {
|
||||
let source = text as NSString
|
||||
let opening = source.lineRange(for: NSRange(location: block.range.location, length: 0))
|
||||
let openingText = source.substring(with: opening).trimmingCharacters(in: .newlines)
|
||||
storage.addAttribute(hiddenMarkup, value: true,
|
||||
range: NSRange(location: opening.location, length: openingText.utf16.count))
|
||||
if NSMaxRange(block.body) < NSMaxRange(block.range) {
|
||||
storage.addAttribute(hiddenMarkup, value: true,
|
||||
range: NSRange(location: NSMaxRange(block.body), length: NSMaxRange(block.range) - NSMaxRange(block.body)))
|
||||
}
|
||||
storage.addAttributes([.font: NSFont.monospacedSystemFont(ofSize: 17, weight: .regular),
|
||||
.foregroundColor: SunsetPalette.secondaryText], range: block.range)
|
||||
storage.addAttribute(.foregroundColor, value: SunsetPalette.text, range: block.body)
|
||||
|
||||
Reference in New Issue
Block a user