Files
calctext/calcpad-macos/Sources/CalcPad/Views/TwoColumnEditorView.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

73 lines
2.6 KiB
Swift

import Combine
import SwiftUI
/// The main two-column editor layout: text editor on the left, results on the right.
/// Scrolling is synchronized between both columns.
struct TwoColumnEditorView: View {
@State private var text: String = ""
@State private var scrollOffset: CGFloat = 0
@State private var results: [LineResult] = []
@State private var evaluationTask: Task<Void, Never>?
/// Uses the Rust FFI engine. Falls back to StubCalculationEngine if the Rust
/// library is not linked (e.g., during UI-only development).
private let engine: CalculationEngine = RustCalculationEngine()
/// Debounce interval for re-evaluation after typing (seconds).
private let evaluationDebounce: TimeInterval = 0.05
/// Font that respects the user's accessibility / Dynamic Type settings.
private var editorFont: NSFont {
// Use the system's preferred monospaced font size, which scales with
// Accessibility > Display > Text Size in System Settings (macOS 14+).
let baseSize = NSFont.systemFontSize
// Scale with accessibility settings via the body text style size
let preferredSize = NSFont.preferredFont(forTextStyle: .body, options: [:]).pointSize
// Use the larger of system default or accessibility-preferred size
let size = max(baseSize, preferredSize)
return NSFont.monospacedSystemFont(ofSize: size, weight: .regular)
}
var body: some View {
HSplitView {
// Left pane: Editor
EditorTextView(
text: $text,
scrollOffset: $scrollOffset,
font: editorFont
)
.frame(minWidth: 200)
// Divider is automatic with HSplitView
// Right pane: Answer column
AnswerColumnView(
results: results,
scrollOffset: $scrollOffset,
font: editorFont
)
.frame(minWidth: 120, idealWidth: 200)
}
.onChange(of: text) { _, newValue in
scheduleEvaluation(newValue)
}
.onAppear {
evaluateText(text)
}
}
/// Debounce evaluation so rapid typing doesn't cause excessive recalculation.
private func scheduleEvaluation(_ newText: String) {
evaluationTask?.cancel()
evaluationTask = Task { @MainActor in
try? await Task.sleep(for: .milliseconds(Int(evaluationDebounce * 1000)))
guard !Task.isCancelled else { return }
evaluateText(newText)
}
}
private func evaluateText(_ newText: String) {
results = engine.evaluateSheet(newText)
}
}