merge integration
This commit is contained in:
commit
a1ca3817ba
|
|
@ -321,85 +321,153 @@ class MarkdownTableView: NSView, NSTextFieldDelegate {
|
||||||
var onTableChanged: ((ParsedTable) -> Void)?
|
var onTableChanged: ((ParsedTable) -> Void)?
|
||||||
|
|
||||||
private var cellFields: [[NSTextField]] = []
|
private var cellFields: [[NSTextField]] = []
|
||||||
private let cellHeight: CGFloat = 26
|
private var columnWidths: [CGFloat] = []
|
||||||
private let cellPadding: CGFloat = 4
|
private var rowHeights: [CGFloat] = []
|
||||||
private let headerHeight: CGFloat = 28
|
private var tableWidth: CGFloat = 0
|
||||||
|
|
||||||
|
private let minColWidth: CGFloat = 40
|
||||||
|
private let minRowHeight: CGFloat = 24
|
||||||
|
private let defaultHeaderHeight: CGFloat = 28
|
||||||
|
private let defaultCellHeight: CGFloat = 26
|
||||||
|
private let dividerHitZone: CGFloat = 6
|
||||||
|
|
||||||
|
private let indicatorRowHeight: CGFloat = 20
|
||||||
|
private let indicatorColWidth: CGFloat = 30
|
||||||
|
private var indicatorsVisible = true
|
||||||
|
private var indicatorContainer: NSView?
|
||||||
|
private var focusMonitor: Any?
|
||||||
|
|
||||||
|
private enum DragMode { case none, column(Int), row(Int) }
|
||||||
|
private var dragMode: DragMode = .none
|
||||||
|
private var dragStartPoint: NSPoint = .zero
|
||||||
|
private var dragStartSize: CGFloat = 0
|
||||||
|
|
||||||
init(table: ParsedTable, width: CGFloat) {
|
init(table: ParsedTable, width: CGFloat) {
|
||||||
self.table = table
|
self.table = table
|
||||||
|
self.tableWidth = width
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
wantsLayer = true
|
wantsLayer = true
|
||||||
layer?.backgroundColor = Theme.current.base.cgColor
|
layer?.backgroundColor = Theme.current.base.cgColor
|
||||||
layer?.cornerRadius = 4
|
layer?.cornerRadius = 4
|
||||||
layer?.borderWidth = 1
|
layer?.borderWidth = 1
|
||||||
layer?.borderColor = Theme.current.surface2.cgColor
|
layer?.borderColor = Theme.current.surface2.cgColor
|
||||||
buildGrid(width: width)
|
initSizes(width: width)
|
||||||
|
buildGrid()
|
||||||
|
setupTrackingArea()
|
||||||
|
setupFocusMonitoring()
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) { fatalError() }
|
required init?(coder: NSCoder) { fatalError() }
|
||||||
|
|
||||||
private func buildGrid(width: CGFloat) {
|
deinit {
|
||||||
|
if let monitor = focusMonitor {
|
||||||
|
NotificationCenter.default.removeObserver(monitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initSizes(width: CGFloat) {
|
||||||
|
let colCount = table.headers.count
|
||||||
|
guard colCount > 0 else { return }
|
||||||
|
let available = width - CGFloat(colCount + 1)
|
||||||
|
let colW = available / CGFloat(colCount)
|
||||||
|
columnWidths = Array(repeating: max(colW, minColWidth), count: colCount)
|
||||||
|
|
||||||
|
rowHeights = []
|
||||||
|
rowHeights.append(defaultHeaderHeight)
|
||||||
|
for _ in 0..<table.rows.count {
|
||||||
|
rowHeights.append(defaultCellHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var totalHeight: CGFloat {
|
||||||
|
rowHeights.reduce(0, +)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func columnX(for col: Int) -> CGFloat {
|
||||||
|
var x: CGFloat = 1
|
||||||
|
for i in 0..<col {
|
||||||
|
x += columnWidths[i] + 1
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
private func rowY(for row: Int) -> CGFloat {
|
||||||
|
let th = totalHeight
|
||||||
|
var y = th
|
||||||
|
for i in 0...row {
|
||||||
|
y -= rowHeights[i]
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
private func buildGrid() {
|
||||||
subviews.forEach { $0.removeFromSuperview() }
|
subviews.forEach { $0.removeFromSuperview() }
|
||||||
|
indicatorContainer = nil
|
||||||
cellFields = []
|
cellFields = []
|
||||||
|
|
||||||
let colCount = table.headers.count
|
let colCount = table.headers.count
|
||||||
guard colCount > 0 else { return }
|
guard colCount > 0 else { return }
|
||||||
let colWidth = (width - CGFloat(colCount + 1)) / CGFloat(colCount)
|
let th = totalHeight
|
||||||
let totalRows = 1 + table.rows.count
|
let ox = indicatorColWidth
|
||||||
let totalHeight = headerHeight + CGFloat(table.rows.count) * cellHeight
|
let oy = indicatorRowHeight
|
||||||
|
let gridWidth = columnX(for: colCount) + 1
|
||||||
|
let fullWidth = gridWidth + ox
|
||||||
|
let fullHeight = th + oy
|
||||||
|
|
||||||
frame.size = NSSize(width: width, height: totalHeight)
|
frame.size = NSSize(width: fullWidth, height: fullHeight)
|
||||||
|
|
||||||
// Header row
|
let headerBg = NSView(frame: NSRect(x: ox, y: th - rowHeights[0] + oy, width: gridWidth, height: rowHeights[0]))
|
||||||
let headerBg = NSView(frame: NSRect(x: 0, y: totalHeight - headerHeight, width: width, height: headerHeight))
|
|
||||||
headerBg.wantsLayer = true
|
headerBg.wantsLayer = true
|
||||||
headerBg.layer?.backgroundColor = Theme.current.surface0.cgColor
|
headerBg.layer?.backgroundColor = Theme.current.surface0.cgColor
|
||||||
addSubview(headerBg)
|
addSubview(headerBg)
|
||||||
|
|
||||||
var headerFields: [NSTextField] = []
|
var headerFields: [NSTextField] = []
|
||||||
for (col, header) in table.headers.enumerated() {
|
for (col, header) in table.headers.enumerated() {
|
||||||
let x = CGFloat(col) * colWidth + CGFloat(col + 1)
|
let x = columnX(for: col) + ox
|
||||||
let field = makeCell(text: header, frame: NSRect(x: x, y: totalHeight - headerHeight + 2, width: colWidth, height: headerHeight - 4), isHeader: true, row: -1, col: col)
|
let h = rowHeights[0]
|
||||||
|
let field = makeCell(text: header, frame: NSRect(x: x, y: th - h + 2 + oy, width: columnWidths[col], height: h - 4), isHeader: true, row: -1, col: col)
|
||||||
addSubview(field)
|
addSubview(field)
|
||||||
headerFields.append(field)
|
headerFields.append(field)
|
||||||
}
|
}
|
||||||
cellFields.append(headerFields)
|
cellFields.append(headerFields)
|
||||||
|
|
||||||
// Data rows
|
|
||||||
for (rowIdx, row) in table.rows.enumerated() {
|
for (rowIdx, row) in table.rows.enumerated() {
|
||||||
var rowFields: [NSTextField] = []
|
var rowFields: [NSTextField] = []
|
||||||
let y = totalHeight - headerHeight - CGFloat(rowIdx + 1) * cellHeight
|
let y = rowY(for: rowIdx + 1) + oy
|
||||||
|
let h = rowHeights[rowIdx + 1]
|
||||||
for (col, cell) in row.enumerated() where col < colCount {
|
for (col, cell) in row.enumerated() where col < colCount {
|
||||||
let x = CGFloat(col) * colWidth + CGFloat(col + 1)
|
let x = columnX(for: col) + ox
|
||||||
let field = makeCell(text: cell, frame: NSRect(x: x, y: y + 2, width: colWidth, height: cellHeight - 4), isHeader: false, row: rowIdx, col: col)
|
let field = makeCell(text: cell, frame: NSRect(x: x, y: y + 2, width: columnWidths[col], height: h - 4), isHeader: false, row: rowIdx, col: col)
|
||||||
addSubview(field)
|
addSubview(field)
|
||||||
rowFields.append(field)
|
rowFields.append(field)
|
||||||
}
|
}
|
||||||
while rowFields.count < colCount {
|
while rowFields.count < colCount {
|
||||||
let col = rowFields.count
|
let col = rowFields.count
|
||||||
let x = CGFloat(col) * colWidth + CGFloat(col + 1)
|
let x = columnX(for: col) + ox
|
||||||
let field = makeCell(text: "", frame: NSRect(x: x, y: y + 2, width: colWidth, height: cellHeight - 4), isHeader: false, row: rowIdx, col: col)
|
let field = makeCell(text: "", frame: NSRect(x: x, y: y + 2, width: columnWidths[col], height: h - 4), isHeader: false, row: rowIdx, col: col)
|
||||||
addSubview(field)
|
addSubview(field)
|
||||||
rowFields.append(field)
|
rowFields.append(field)
|
||||||
}
|
}
|
||||||
cellFields.append(rowFields)
|
cellFields.append(rowFields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grid lines
|
let totalRows = 1 + table.rows.count
|
||||||
for i in 1..<colCount {
|
for i in 1..<colCount {
|
||||||
let x = CGFloat(i) * (colWidth + 1)
|
let x = columnX(for: i) - 1 + ox
|
||||||
let line = NSView(frame: NSRect(x: x, y: 0, width: 1, height: totalHeight))
|
let line = NSView(frame: NSRect(x: x, y: oy, width: 1, height: th))
|
||||||
line.wantsLayer = true
|
line.wantsLayer = true
|
||||||
line.layer?.backgroundColor = Theme.current.surface2.cgColor
|
line.layer?.backgroundColor = Theme.current.surface2.cgColor
|
||||||
addSubview(line)
|
addSubview(line)
|
||||||
}
|
}
|
||||||
for i in 0..<totalRows {
|
for i in 0..<totalRows {
|
||||||
let y = totalHeight - headerHeight - CGFloat(i) * cellHeight
|
let lineY = ((i == 0) ? th - rowHeights[0] : rowY(for: i)) + oy
|
||||||
let line = NSView(frame: NSRect(x: 0, y: y, width: width, height: 1))
|
let line = NSView(frame: NSRect(x: ox, y: lineY, width: gridWidth, height: 1))
|
||||||
line.wantsLayer = true
|
line.wantsLayer = true
|
||||||
line.layer?.backgroundColor = Theme.current.surface2.cgColor
|
line.layer?.backgroundColor = Theme.current.surface2.cgColor
|
||||||
addSubview(line)
|
addSubview(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildIndicators()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeCell(text: String, frame: NSRect, isHeader: Bool, row: Int, col: Int) -> NSTextField {
|
private func makeCell(text: String, frame: NSRect, isHeader: Bool, row: Int, col: Int) -> NSTextField {
|
||||||
|
|
@ -426,6 +494,290 @@ class MarkdownTableView: NSView, NSTextFieldDelegate {
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Column letter helper
|
||||||
|
|
||||||
|
private func columnLetter(for index: Int) -> String {
|
||||||
|
var n = index
|
||||||
|
var result = ""
|
||||||
|
repeat {
|
||||||
|
result = String(UnicodeScalar(65 + (n % 26))!) + result
|
||||||
|
n = n / 26 - 1
|
||||||
|
} while n >= 0
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Indicators
|
||||||
|
|
||||||
|
private func buildIndicators() {
|
||||||
|
indicatorContainer?.removeFromSuperview()
|
||||||
|
|
||||||
|
let container = NSView(frame: bounds)
|
||||||
|
container.wantsLayer = true
|
||||||
|
container.alphaValue = indicatorsVisible ? 1 : 0
|
||||||
|
addSubview(container)
|
||||||
|
indicatorContainer = container
|
||||||
|
|
||||||
|
let colCount = table.headers.count
|
||||||
|
let totalRows = 1 + table.rows.count
|
||||||
|
let topY = totalHeight + indicatorRowHeight
|
||||||
|
let indicatorBg = Theme.current.surface0
|
||||||
|
|
||||||
|
let corner = NSView(frame: NSRect(x: 0, y: topY - indicatorRowHeight, width: indicatorColWidth, height: indicatorRowHeight))
|
||||||
|
corner.wantsLayer = true
|
||||||
|
corner.layer?.backgroundColor = indicatorBg.cgColor
|
||||||
|
container.addSubview(corner)
|
||||||
|
|
||||||
|
for col in 0..<colCount {
|
||||||
|
let x = indicatorColWidth + columnX(for: col)
|
||||||
|
let label = NSTextField(labelWithString: columnLetter(for: col))
|
||||||
|
label.font = NSFont.systemFont(ofSize: 10, weight: .medium)
|
||||||
|
label.textColor = Theme.current.overlay2
|
||||||
|
label.alignment = .center
|
||||||
|
label.frame = NSRect(x: x, y: topY - indicatorRowHeight, width: columnWidths[col], height: indicatorRowHeight)
|
||||||
|
label.wantsLayer = true
|
||||||
|
label.layer?.backgroundColor = indicatorBg.cgColor
|
||||||
|
container.addSubview(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in 0..<totalRows {
|
||||||
|
let y = rowY(for: row) + indicatorRowHeight
|
||||||
|
let label = NSTextField(labelWithString: "\(row + 1)")
|
||||||
|
label.font = NSFont.systemFont(ofSize: 10, weight: .medium)
|
||||||
|
label.textColor = Theme.current.overlay2
|
||||||
|
label.alignment = .center
|
||||||
|
label.frame = NSRect(x: 0, y: y, width: indicatorColWidth, height: rowHeights[row])
|
||||||
|
label.wantsLayer = true
|
||||||
|
label.layer?.backgroundColor = indicatorBg.cgColor
|
||||||
|
container.addSubview(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showIndicators() {
|
||||||
|
guard !indicatorsVisible else { return }
|
||||||
|
indicatorsVisible = true
|
||||||
|
NSAnimationContext.runAnimationGroup { ctx in
|
||||||
|
ctx.duration = 0.2
|
||||||
|
self.indicatorContainer?.animator().alphaValue = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hideIndicators() {
|
||||||
|
guard indicatorsVisible else { return }
|
||||||
|
indicatorsVisible = false
|
||||||
|
NSAnimationContext.runAnimationGroup { ctx in
|
||||||
|
ctx.duration = 0.2
|
||||||
|
self.indicatorContainer?.animator().alphaValue = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupFocusMonitoring() {
|
||||||
|
focusMonitor = NotificationCenter.default.addObserver(
|
||||||
|
forName: NSWindow.didUpdateNotification,
|
||||||
|
object: nil,
|
||||||
|
queue: .main
|
||||||
|
) { [weak self] _ in
|
||||||
|
self?.checkFocusState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkFocusState() {
|
||||||
|
guard let w = window else { return }
|
||||||
|
let fr = w.firstResponder
|
||||||
|
var hasFocusedCell = false
|
||||||
|
if let fieldEditor = fr as? NSTextView,
|
||||||
|
let editedCell = fieldEditor.delegate as AnyObject? {
|
||||||
|
hasFocusedCell = cellFields.joined().contains { $0 === editedCell }
|
||||||
|
} else {
|
||||||
|
hasFocusedCell = cellFields.joined().contains { $0 === fr }
|
||||||
|
}
|
||||||
|
if hasFocusedCell && !indicatorsVisible {
|
||||||
|
showIndicators()
|
||||||
|
} else if !hasFocusedCell && indicatorsVisible && !mouseInside {
|
||||||
|
hideIndicators()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mouseInside = false
|
||||||
|
|
||||||
|
// MARK: - Resize hit detection
|
||||||
|
|
||||||
|
private func columnDivider(at point: NSPoint) -> Int? {
|
||||||
|
let colCount = table.headers.count
|
||||||
|
for i in 1..<colCount {
|
||||||
|
let divX = columnX(for: i) - 1 + indicatorColWidth
|
||||||
|
if abs(point.x - divX) <= dividerHitZone {
|
||||||
|
return i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let lastX = columnX(for: colCount) + indicatorColWidth
|
||||||
|
if abs(point.x - lastX) <= dividerHitZone {
|
||||||
|
return colCount - 1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func rowDivider(at point: NSPoint) -> Int? {
|
||||||
|
let totalRows = 1 + table.rows.count
|
||||||
|
for i in 0..<totalRows {
|
||||||
|
let divY: CGFloat
|
||||||
|
if i == 0 {
|
||||||
|
divY = totalHeight - rowHeights[0] + indicatorRowHeight
|
||||||
|
} else {
|
||||||
|
divY = rowY(for: i) + indicatorRowHeight
|
||||||
|
}
|
||||||
|
if abs(point.y - divY) <= dividerHitZone {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Tracking area
|
||||||
|
|
||||||
|
private func setupTrackingArea() {
|
||||||
|
for area in trackingAreas { removeTrackingArea(area) }
|
||||||
|
let area = NSTrackingArea(
|
||||||
|
rect: bounds,
|
||||||
|
options: [.mouseMoved, .mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect],
|
||||||
|
owner: self,
|
||||||
|
userInfo: nil
|
||||||
|
)
|
||||||
|
addTrackingArea(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseMoved(with event: NSEvent) {
|
||||||
|
let pt = convert(event.locationInWindow, from: nil)
|
||||||
|
if columnDivider(at: pt) != nil {
|
||||||
|
NSCursor.resizeLeftRight.set()
|
||||||
|
} else if rowDivider(at: pt) != nil {
|
||||||
|
NSCursor.resizeUpDown.set()
|
||||||
|
} else {
|
||||||
|
NSCursor.arrow.set()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseEntered(with event: NSEvent) {
|
||||||
|
mouseInside = true
|
||||||
|
showIndicators()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseExited(with event: NSEvent) {
|
||||||
|
NSCursor.arrow.set()
|
||||||
|
mouseInside = false
|
||||||
|
checkFocusState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseDown(with event: NSEvent) {
|
||||||
|
if !indicatorsVisible {
|
||||||
|
mouseInside = true
|
||||||
|
showIndicators()
|
||||||
|
}
|
||||||
|
let pt = convert(event.locationInWindow, from: nil)
|
||||||
|
if let col = columnDivider(at: pt) {
|
||||||
|
if event.clickCount == 2 {
|
||||||
|
autoFitColumn(col)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dragMode = .column(col)
|
||||||
|
dragStartPoint = pt
|
||||||
|
dragStartSize = columnWidths[col]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let row = rowDivider(at: pt) {
|
||||||
|
if event.clickCount == 2 {
|
||||||
|
autoFitRow(row)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dragMode = .row(row)
|
||||||
|
dragStartPoint = pt
|
||||||
|
dragStartSize = rowHeights[row]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dragMode = .none
|
||||||
|
super.mouseDown(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseDragged(with event: NSEvent) {
|
||||||
|
let pt = convert(event.locationInWindow, from: nil)
|
||||||
|
switch dragMode {
|
||||||
|
case .column(let col):
|
||||||
|
let delta = pt.x - dragStartPoint.x
|
||||||
|
columnWidths[col] = max(minColWidth, dragStartSize + delta)
|
||||||
|
buildGrid()
|
||||||
|
case .row(let row):
|
||||||
|
let delta = dragStartPoint.y - pt.y
|
||||||
|
rowHeights[row] = max(minRowHeight, dragStartSize + delta)
|
||||||
|
buildGrid()
|
||||||
|
case .none:
|
||||||
|
super.mouseDragged(with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseUp(with event: NSEvent) {
|
||||||
|
if case .none = dragMode {
|
||||||
|
super.mouseUp(with: event)
|
||||||
|
}
|
||||||
|
dragMode = .none
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Auto-fit
|
||||||
|
|
||||||
|
private func measureColumnWidth(_ col: Int) -> CGFloat {
|
||||||
|
let headerFont = NSFontManager.shared.convert(Theme.editorFont, toHaveTrait: .boldFontMask)
|
||||||
|
let cellFont = Theme.editorFont
|
||||||
|
|
||||||
|
var maxW: CGFloat = 0
|
||||||
|
if col < table.headers.count {
|
||||||
|
let w = (table.headers[col] as NSString).size(withAttributes: [.font: headerFont]).width
|
||||||
|
maxW = max(maxW, w)
|
||||||
|
}
|
||||||
|
for row in table.rows {
|
||||||
|
guard col < row.count else { continue }
|
||||||
|
let w = (row[col] as NSString).size(withAttributes: [.font: cellFont]).width
|
||||||
|
maxW = max(maxW, w)
|
||||||
|
}
|
||||||
|
return max(maxW + 16, minColWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func measureRowHeight(_ row: Int) -> CGFloat {
|
||||||
|
let font: NSFont = row == 0
|
||||||
|
? NSFontManager.shared.convert(Theme.editorFont, toHaveTrait: .boldFontMask)
|
||||||
|
: Theme.editorFont
|
||||||
|
let cells: [String] = row == 0 ? table.headers : (row - 1 < table.rows.count ? table.rows[row - 1] : [])
|
||||||
|
|
||||||
|
var maxH: CGFloat = 0
|
||||||
|
for (col, text) in cells.enumerated() where col < columnWidths.count {
|
||||||
|
let constrainedSize = NSSize(width: columnWidths[col], height: .greatestFiniteMagnitude)
|
||||||
|
let rect = (text as NSString).boundingRect(
|
||||||
|
with: constrainedSize,
|
||||||
|
options: [.usesLineFragmentOrigin],
|
||||||
|
attributes: [.font: font]
|
||||||
|
)
|
||||||
|
maxH = max(maxH, rect.height)
|
||||||
|
}
|
||||||
|
return max(maxH + 8, minRowHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func autoFitColumn(_ col: Int) {
|
||||||
|
guard col >= 0, col < columnWidths.count else { return }
|
||||||
|
columnWidths[col] = measureColumnWidth(col)
|
||||||
|
buildGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func autoFitRow(_ row: Int) {
|
||||||
|
guard row >= 0, row < rowHeights.count else { return }
|
||||||
|
rowHeights[row] = measureRowHeight(row)
|
||||||
|
buildGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Cell editing
|
||||||
|
|
||||||
|
func controlTextDidBeginEditing(_ obj: Notification) {
|
||||||
|
if !indicatorsVisible {
|
||||||
|
showIndicators()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func controlTextDidEndEditing(_ obj: Notification) {
|
func controlTextDidEndEditing(_ obj: Notification) {
|
||||||
guard let field = obj.object as? NSTextField else { return }
|
guard let field = obj.object as? NSTextField else { return }
|
||||||
let tag = field.tag
|
let tag = field.tag
|
||||||
|
|
@ -464,8 +816,8 @@ class MarkdownTableView: NSView, NSTextFieldDelegate {
|
||||||
table.rows.append(Array(repeating: "", count: table.headers.count))
|
table.rows.append(Array(repeating: "", count: table.headers.count))
|
||||||
onTableChanged?(table)
|
onTableChanged?(table)
|
||||||
|
|
||||||
let width = frame.width
|
rowHeights.append(defaultCellHeight)
|
||||||
buildGrid(width: width)
|
buildGrid()
|
||||||
needsLayout = true
|
needsLayout = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -1530,27 +1882,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) {
|
||||||
let monoFont = NSFont.monospacedSystemFont(ofSize: baseFont.pointSize, weight: .regular)
|
textStorage.addAttribute(.foregroundColor, value: palette.base, range: lineRange)
|
||||||
let boldMono = NSFontManager.shared.convert(monoFont, toHaveTrait: .boldFontMask)
|
textStorage.addAttribute(.font, value: NSFont.systemFont(ofSize: 0.01), range: lineRange)
|
||||||
|
|
||||||
textStorage.addAttribute(.font, value: monoFont, range: lineRange)
|
|
||||||
textStorage.addAttribute(.foregroundColor, value: palette.text, range: lineRange)
|
|
||||||
|
|
||||||
if isSeparator {
|
|
||||||
textStorage.addAttribute(.foregroundColor, value: palette.overlay0, range: lineRange)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isHeader {
|
|
||||||
textStorage.addAttribute(.font, value: boldMono, range: lineRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mute pipe delimiters
|
|
||||||
guard let pipeRegex = try? NSRegularExpression(pattern: "\\|") else { return }
|
|
||||||
let matches = pipeRegex.matches(in: textStorage.string, range: lineRange)
|
|
||||||
for match in matches {
|
|
||||||
textStorage.addAttribute(.foregroundColor, value: palette.overlay0, range: match.range)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Lists and Horizontal Rules
|
// MARK: - Lists and Horizontal Rules
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue