fix table rendering: preserve layout size, skip raw glyphs, draw background
This commit is contained in:
parent
db1a0aaefa
commit
fef2935ae5
|
|
@ -36,8 +36,10 @@ class MarkdownLayoutManager: NSLayoutManager {
|
||||||
drawBlockquoteBorder(glyphRange: glyphRange, origin: origin, container: textContainer)
|
drawBlockquoteBorder(glyphRange: glyphRange, origin: origin, container: textContainer)
|
||||||
case .horizontalRule:
|
case .horizontalRule:
|
||||||
drawHorizontalRule(glyphRange: glyphRange, origin: origin, container: textContainer)
|
drawHorizontalRule(glyphRange: glyphRange, origin: origin, container: textContainer)
|
||||||
case .checkbox, .tableBlock:
|
case .checkbox:
|
||||||
break
|
break
|
||||||
|
case .tableBlock:
|
||||||
|
drawTableBackground(glyphRange: glyphRange, origin: origin, container: textContainer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +61,8 @@ class MarkdownLayoutManager: NSLayoutManager {
|
||||||
drawCheckbox(checked: checked, glyphRange: glyphRange, origin: origin, container: textContainer)
|
drawCheckbox(checked: checked, glyphRange: glyphRange, origin: origin, container: textContainer)
|
||||||
case .horizontalRule:
|
case .horizontalRule:
|
||||||
skipRanges.append(glyphRange)
|
skipRanges.append(glyphRange)
|
||||||
|
case .tableBlock:
|
||||||
|
skipRanges.append(glyphRange)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +156,17 @@ class MarkdownLayoutManager: NSLayoutManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func drawTableBackground(glyphRange: NSRange, origin: NSPoint, container: NSTextContainer) {
|
||||||
|
var rect = boundingRect(forGlyphRange: glyphRange, in: container)
|
||||||
|
rect.origin.x = origin.x + 4
|
||||||
|
rect.origin.y += origin.y
|
||||||
|
rect.size.width = container.containerSize.width - 8
|
||||||
|
|
||||||
|
let path = NSBezierPath(roundedRect: rect, xRadius: 4, yRadius: 4)
|
||||||
|
Theme.current.base.setFill()
|
||||||
|
path.fill()
|
||||||
|
}
|
||||||
|
|
||||||
private func drawTableBorders(glyphRange: NSRange, columns: Int, origin: NSPoint, container: NSTextContainer) {
|
private func drawTableBorders(glyphRange: NSRange, columns: Int, origin: NSPoint, container: NSTextContainer) {
|
||||||
guard columns > 0 else { return }
|
guard columns > 0 else { return }
|
||||||
var rect = boundingRect(forGlyphRange: glyphRange, in: container)
|
var rect = boundingRect(forGlyphRange: glyphRange, in: container)
|
||||||
|
|
@ -2434,8 +2449,8 @@ private func highlightFootnoteDefinition(textStorage: NSTextStorage, lineRange:
|
||||||
// MARK: - Tables
|
// MARK: - Tables
|
||||||
|
|
||||||
private func highlightTableLine(_ trimmed: String, lineRange: NSRange, textStorage: NSTextStorage, palette: CatppuccinPalette, baseFont: NSFont, isHeader: Bool, isSeparator: Bool) {
|
private func highlightTableLine(_ trimmed: String, lineRange: NSRange, textStorage: NSTextStorage, palette: CatppuccinPalette, baseFont: NSFont, isHeader: Bool, isSeparator: Bool) {
|
||||||
textStorage.addAttribute(.foregroundColor, value: palette.base, range: lineRange)
|
textStorage.addAttribute(.foregroundColor, value: NSColor.clear, range: lineRange)
|
||||||
textStorage.addAttribute(.font, value: NSFont.systemFont(ofSize: 0.01), range: lineRange)
|
textStorage.addAttribute(.font, value: baseFont, range: lineRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Lists and Horizontal Rules
|
// MARK: - Lists and Horizontal Rules
|
||||||
|
|
@ -2753,7 +2768,11 @@ private func resolveLocalImagePath(_ rawPath: String) -> String? {
|
||||||
|
|
||||||
class LineNumberTextView: NSTextView {
|
class LineNumberTextView: NSTextView {
|
||||||
static let gutterWidth: CGFloat = 50
|
static let gutterWidth: CGFloat = 50
|
||||||
var evalResults: [Int: EvalEntry] = [:]
|
static let evalLeftMargin: CGFloat = 80
|
||||||
|
|
||||||
|
var evalResults: [Int: EvalEntry] = [:] {
|
||||||
|
didSet { applyEvalSpacing() }
|
||||||
|
}
|
||||||
|
|
||||||
override var textContainerOrigin: NSPoint {
|
override var textContainerOrigin: NSPoint {
|
||||||
return NSPoint(x: LineNumberTextView.gutterWidth, y: textContainerInset.height)
|
return NSPoint(x: LineNumberTextView.gutterWidth, y: textContainerInset.height)
|
||||||
|
|
@ -2857,9 +2876,9 @@ class LineNumberTextView: NSTextView {
|
||||||
if let entry = evalResults[lineNumber - 1] {
|
if let entry = evalResults[lineNumber - 1] {
|
||||||
switch entry.format {
|
switch entry.format {
|
||||||
case .table:
|
case .table:
|
||||||
drawTableResult(entry.result, at: y, origin: origin, resultAttrs: resultAttrs)
|
drawTableResult(entry.result, lineRect: lineRect, origin: origin, resultAttrs: resultAttrs)
|
||||||
case .tree:
|
case .tree:
|
||||||
drawTreeResult(entry.result, at: y, origin: origin, resultAttrs: resultAttrs)
|
drawTreeResult(entry.result, lineRect: lineRect, origin: origin, resultAttrs: resultAttrs)
|
||||||
case .inline:
|
case .inline:
|
||||||
let resultStr = NSAttributedString(string: "\u{2192} \(entry.result)", attributes: resultAttrs)
|
let resultStr = NSAttributedString(string: "\u{2192} \(entry.result)", attributes: resultAttrs)
|
||||||
let size = resultStr.size()
|
let size = resultStr.size()
|
||||||
|
|
@ -2875,13 +2894,13 @@ class LineNumberTextView: NSTextView {
|
||||||
|
|
||||||
// MARK: - Table/Tree Rendering
|
// MARK: - Table/Tree Rendering
|
||||||
|
|
||||||
private func drawTableResult(_ json: String, at y: CGFloat, origin: NSPoint, resultAttrs: [NSAttributedString.Key: Any]) {
|
private func drawTableResult(_ json: String, lineRect: NSRect, origin: NSPoint, resultAttrs: [NSAttributedString.Key: Any]) {
|
||||||
guard let data = json.data(using: .utf8),
|
guard let data = json.data(using: .utf8),
|
||||||
let parsed = try? JSONSerialization.jsonObject(with: data),
|
let parsed = try? JSONSerialization.jsonObject(with: data),
|
||||||
let rows = parsed as? [[Any]] else {
|
let rows = parsed as? [[Any]] else {
|
||||||
let fallback = NSAttributedString(string: "\u{2192} \(json.prefix(40))", attributes: resultAttrs)
|
let fallback = NSAttributedString(string: "\u{2192} \(json.prefix(40))", attributes: resultAttrs)
|
||||||
let size = fallback.size()
|
let size = fallback.size()
|
||||||
fallback.draw(at: NSPoint(x: visibleRect.maxX - size.width - 8, y: y))
|
fallback.draw(at: NSPoint(x: visibleRect.maxX - size.width - 8, y: lineRect.origin.y + origin.y))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2922,9 +2941,8 @@ class LineNumberTextView: NSTextView {
|
||||||
let tableWidth = colWidths.reduce(0, +) + cellPad * CGFloat(colCount + 1) + CGFloat(colCount - 1)
|
let tableWidth = colWidths.reduce(0, +) + cellPad * CGFloat(colCount + 1) + CGFloat(colCount - 1)
|
||||||
let tableHeight = rowHeight * CGFloat(stringRows.count) + CGFloat(stringRows.count + 1)
|
let tableHeight = rowHeight * CGFloat(stringRows.count) + CGFloat(stringRows.count + 1)
|
||||||
|
|
||||||
let rightEdge = visibleRect.maxX
|
let tableX = LineNumberTextView.evalLeftMargin
|
||||||
let tableX = rightEdge - tableWidth - 12
|
let tableY = lineRect.origin.y + origin.y + lineRect.height + 4
|
||||||
let tableY = y
|
|
||||||
|
|
||||||
let tableRect = NSRect(x: tableX, y: tableY, width: tableWidth, height: tableHeight)
|
let tableRect = NSRect(x: tableX, y: tableY, width: tableWidth, height: tableHeight)
|
||||||
palette.mantle.setFill()
|
palette.mantle.setFill()
|
||||||
|
|
@ -2956,12 +2974,12 @@ class LineNumberTextView: NSTextView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func drawTreeResult(_ json: String, at y: CGFloat, origin: NSPoint, resultAttrs: [NSAttributedString.Key: Any]) {
|
private func drawTreeResult(_ json: String, lineRect: NSRect, origin: NSPoint, resultAttrs: [NSAttributedString.Key: Any]) {
|
||||||
guard let data = json.data(using: .utf8),
|
guard let data = json.data(using: .utf8),
|
||||||
let root = try? JSONSerialization.jsonObject(with: data) else {
|
let root = try? JSONSerialization.jsonObject(with: data) else {
|
||||||
let fallback = NSAttributedString(string: "\u{2192} \(json.prefix(40))", attributes: resultAttrs)
|
let fallback = NSAttributedString(string: "\u{2192} \(json.prefix(40))", attributes: resultAttrs)
|
||||||
let size = fallback.size()
|
let size = fallback.size()
|
||||||
fallback.draw(at: NSPoint(x: visibleRect.maxX - size.width - 8, y: y))
|
fallback.draw(at: NSPoint(x: visibleRect.maxX - size.width - 8, y: lineRect.origin.y + origin.y))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3010,9 +3028,8 @@ class LineNumberTextView: NSTextView {
|
||||||
|
|
||||||
let treeHeight = lineHeight * CGFloat(lines.count) + 4
|
let treeHeight = lineHeight * CGFloat(lines.count) + 4
|
||||||
let treeWidth = maxWidth + 16
|
let treeWidth = maxWidth + 16
|
||||||
let rightEdge = visibleRect.maxX
|
let treeX = LineNumberTextView.evalLeftMargin
|
||||||
let treeX = rightEdge - treeWidth - 8
|
let treeY = lineRect.origin.y + origin.y + lineRect.height + 4
|
||||||
let treeY = y
|
|
||||||
|
|
||||||
let treeRect = NSRect(x: treeX, y: treeY, width: treeWidth, height: treeHeight)
|
let treeRect = NSRect(x: treeX, y: treeY, width: treeWidth, height: treeHeight)
|
||||||
palette.mantle.setFill()
|
palette.mantle.setFill()
|
||||||
|
|
@ -3032,6 +3049,79 @@ class LineNumberTextView: NSTextView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Eval Spacing
|
||||||
|
|
||||||
|
private func applyEvalSpacing() {
|
||||||
|
guard let ts = textStorage else { return }
|
||||||
|
let text = ts.string as NSString
|
||||||
|
guard text.length > 0 else { return }
|
||||||
|
|
||||||
|
ts.beginEditing()
|
||||||
|
|
||||||
|
var lineStart = 0
|
||||||
|
var lineNum = 0
|
||||||
|
while lineStart < text.length {
|
||||||
|
let lineRange = text.lineRange(for: NSRange(location: lineStart, length: 0))
|
||||||
|
if let entry = evalResults[lineNum] {
|
||||||
|
let spacing: CGFloat
|
||||||
|
switch entry.format {
|
||||||
|
case .tree:
|
||||||
|
spacing = evalTreeHeight(entry.result) + 8
|
||||||
|
case .table:
|
||||||
|
spacing = evalTableHeight(entry.result) + 8
|
||||||
|
case .inline:
|
||||||
|
spacing = 0
|
||||||
|
}
|
||||||
|
if spacing > 0 {
|
||||||
|
let para = NSMutableParagraphStyle()
|
||||||
|
if let existing = ts.attribute(.paragraphStyle, at: lineRange.location, effectiveRange: nil) as? NSParagraphStyle {
|
||||||
|
para.setParagraphStyle(existing)
|
||||||
|
}
|
||||||
|
para.paragraphSpacing = spacing
|
||||||
|
ts.addAttribute(.paragraphStyle, value: para, range: lineRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lineNum += 1
|
||||||
|
lineStart = NSMaxRange(lineRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.endEditing()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func evalTreeHeight(_ json: String) -> CGFloat {
|
||||||
|
guard let data = json.data(using: .utf8),
|
||||||
|
let root = try? JSONSerialization.jsonObject(with: data) else { return 0 }
|
||||||
|
let font = Theme.gutterFont
|
||||||
|
let lineHeight = font.pointSize + 4
|
||||||
|
var count = 0
|
||||||
|
func walk(_ node: Any) {
|
||||||
|
if let arr = node as? [Any] {
|
||||||
|
for item in arr {
|
||||||
|
count += 1
|
||||||
|
if item is [Any] { walk(item) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if root is [Any] {
|
||||||
|
count = 1
|
||||||
|
walk(root)
|
||||||
|
} else {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
return lineHeight * CGFloat(count) + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
private func evalTableHeight(_ json: String) -> CGFloat {
|
||||||
|
guard let data = json.data(using: .utf8),
|
||||||
|
let parsed = try? JSONSerialization.jsonObject(with: data),
|
||||||
|
let rows = parsed as? [[Any]] else { return 0 }
|
||||||
|
let font = Theme.gutterFont
|
||||||
|
let rowHeight = font.pointSize + 6
|
||||||
|
return rowHeight * CGFloat(rows.count) + CGFloat(rows.count + 1)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Paste
|
// MARK: - Paste
|
||||||
|
|
||||||
override func paste(_ sender: Any?) {
|
override func paste(_ sender: Any?) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue