Files
calctext/calcpad-macos/Sources/CalcPad/Views/AnswerColumnView.swift
C. Cassel 806e2f1ec6 feat: add platform shells, CLI, formatting, plugins, tests, and benchmarks
Phase 4 — Platform shells:
- calcpad-macos/: SwiftUI two-column editor with Rust FFI bridge (16 files)
- calcpad-windows/: iced GUI with Windows 11 Fluent theme (7 files, 13 tests)
- calcpad-web/: React 18 + CodeMirror 6 + WASM Worker + PWA (20 files)
- calcpad-cli/: clap-based CLI with expression eval, pipe/stdin, JSON/CSV
  output, and interactive REPL with rustyline history

Phase 5 — Engine modules:
- formatting/: answer formatting (decimal/scientific/SI notation, thousands
  separators, currency), line type classification, clipboard values (93 tests)
- plugins/: CalcPadPlugin trait, PluginRegistry, Rhai scripting stub (43 tests)
- benches/: criterion benchmarks (single-line, 100/500-line sheets, DAG, incremental)
- tests/sheet_scenarios.rs: 20 real-world integration tests
- tests/proptest_fuzz.rs: 12 property-based fuzz tests

771 tests passing across workspace, 0 failures.
2026-03-17 09:46:40 -04:00

112 lines
3.9 KiB
Swift

import AppKit
import SwiftUI
/// Displays calculation results in a vertical column, one result per line,
/// aligned to match the corresponding editor lines.
/// Uses NSViewRepresentable wrapping NSScrollView + NSTextView for pixel-perfect
/// line height alignment with the editor.
struct AnswerColumnView: NSViewRepresentable {
let results: [LineResult]
@Binding var scrollOffset: CGFloat
var font: NSFont
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeNSView(context: Context) -> NSScrollView {
let scrollView = NSScrollView()
scrollView.hasVerticalScroller = false
scrollView.hasHorizontalScroller = false
scrollView.borderType = .noBorder
scrollView.drawsBackground = false
// Disable user scrolling scroll is driven by the editor
scrollView.verticalScrollElasticity = .none
scrollView.horizontalScrollElasticity = .none
let textView = NSTextView()
textView.isEditable = false
textView.isSelectable = true
textView.isRichText = true
textView.usesFontPanel = false
textView.drawsBackground = false
textView.backgroundColor = .clear
// Match editor text container settings for alignment
textView.textContainer?.lineFragmentPadding = 4
textView.textContainerInset = NSSize(width: 8, height: 8)
// Disable line wrapping to match editor behavior
textView.isHorizontallyResizable = true
textView.textContainer?.widthTracksTextView = false
textView.textContainer?.containerSize = NSSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
)
textView.maxSize = NSSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
)
scrollView.documentView = textView
context.coordinator.textView = textView
context.coordinator.scrollView = scrollView
updateContent(textView: textView)
return scrollView
}
func updateNSView(_ scrollView: NSScrollView, context: Context) {
guard let textView = scrollView.documentView as? NSTextView else { return }
updateContent(textView: textView)
// Sync scroll position from editor
let currentOffset = scrollView.contentView.bounds.origin.y
if abs(currentOffset - scrollOffset) > 0.5 {
scrollView.contentView.scroll(to: NSPoint(x: 0, y: scrollOffset))
scrollView.reflectScrolledClipView(scrollView.contentView)
}
}
private func updateContent(textView: NSTextView) {
let attributedString = NSMutableAttributedString()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .right
let resultColor = NSColor.secondaryLabelColor
let errorColor = NSColor.systemRed
for (index, lineResult) in results.enumerated() {
let displayText = lineResult.result ?? ""
let color = lineResult.isError ? errorColor : resultColor
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: color,
.paragraphStyle: paragraphStyle,
]
let line = NSAttributedString(string: displayText, attributes: attributes)
attributedString.append(line)
// Add newline between lines (but not after the last)
if index < results.count - 1 {
let newline = NSAttributedString(
string: "\n",
attributes: [.font: font]
)
attributedString.append(newline)
}
}
textView.textStorage?.setAttributedString(attributedString)
}
final class Coordinator {
weak var textView: NSTextView?
weak var scrollView: NSScrollView?
}
}