diff --git a/StikJIT/JSSupport/CodeEditor.swift b/StikJIT/JSSupport/CodeEditor.swift new file mode 100644 index 00000000..06523076 --- /dev/null +++ b/StikJIT/JSSupport/CodeEditor.swift @@ -0,0 +1,79 @@ +import SwiftUI +import UIKit + +class LineNumberTextView: UITextView { + private let lineNumberView = UITextView() + private let lineNumberWidth: CGFloat = 40 + + override var text: String! { + didSet { updateLineNumbers() } + } + + override var font: UIFont? { + didSet { lineNumberView.font = font } + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + setup() + } + + private func setup() { + lineNumberView.backgroundColor = UIColor(white: 0.95, alpha: 1.0) + lineNumberView.textColor = .gray + lineNumberView.isEditable = false + lineNumberView.textAlignment = .right + lineNumberView.isScrollEnabled = false + addSubview(lineNumberView) + textContainerInset.left = lineNumberWidth + 4 + } + + override func layoutSubviews() { + super.layoutSubviews() + lineNumberView.frame = CGRect(x: 0, y: 0, width: lineNumberWidth, height: bounds.height) + } + + func updateLineNumbers() { + let lineCount = text.components(separatedBy: "\n").count + lineNumberView.text = (1...max(lineCount,1)).map { String($0) }.joined(separator: "\n") + } +} + +struct CodeEditor: UIViewRepresentable { + @Binding var text: String + + func makeCoordinator() -> Coordinator { Coordinator(self) } + + func makeUIView(context: Context) -> LineNumberTextView { + let textView = LineNumberTextView() + textView.delegate = context.coordinator + textView.font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular) + textView.autocorrectionType = .no + textView.autocapitalizationType = .none + textView.smartDashesType = .no + textView.smartQuotesType = .no + textView.backgroundColor = UIColor.systemBackground + return textView + } + + func updateUIView(_ uiView: LineNumberTextView, context: Context) { + if uiView.text != text { + uiView.text = text + uiView.updateLineNumbers() + } + } + + class Coordinator: NSObject, UITextViewDelegate { + var parent: CodeEditor + init(_ parent: CodeEditor) { self.parent = parent } + func textViewDidChange(_ textView: UITextView) { + parent.text = textView.text + if let ln = textView as? LineNumberTextView { ln.updateLineNumbers() } + } + } +} diff --git a/StikJIT/JSSupport/ScriptEditorView.swift b/StikJIT/JSSupport/ScriptEditorView.swift index 5c98b649..66226bf7 100644 --- a/StikJIT/JSSupport/ScriptEditorView.swift +++ b/StikJIT/JSSupport/ScriptEditorView.swift @@ -7,6 +7,8 @@ import SwiftUI +/// Custom code editor with line numbers styled like Xcode. + struct ScriptEditorView: View { let scriptURL: URL @State private var scriptContent: String = "" @@ -14,9 +16,9 @@ struct ScriptEditorView: View { var body: some View { VStack { - TextEditor(text: $scriptContent) - .padding() - .border(Color.gray, width: 1) + CodeEditor(text: $scriptContent) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.gray)) .navigationTitle(scriptURL.lastPathComponent) .navigationBarTitleDisplayMode(.inline) .font(.system(.footnote, design: .monospaced))