Improve JSON editor for iOS
This commit is contained in:
@@ -29,26 +29,30 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewBuilder {
|
||||
if readOnly {
|
||||
TextEditor(text: .constant(viewModel.profileContent))
|
||||
} else {
|
||||
TextEditor(text: $viewModel.profileContent)
|
||||
}
|
||||
}
|
||||
.font(Font.system(.caption2, design: .monospaced))
|
||||
.autocorrectionDisabled(true)
|
||||
.textContentType(.init(rawValue: ""))
|
||||
#if os(iOS)
|
||||
.keyboardType(.asciiCapable)
|
||||
.textInputAutocapitalization(.none)
|
||||
.background(Color(UIColor.secondarySystemGroupedBackground))
|
||||
#elseif os(macOS)
|
||||
.padding()
|
||||
#endif
|
||||
RunestoneTextView(
|
||||
text: readOnly ? .constant(viewModel.profileContent) : $viewModel.profileContent,
|
||||
isEditable: !readOnly
|
||||
)
|
||||
.onChangeCompat(of: viewModel.profileContent) {
|
||||
viewModel.markAsChanged()
|
||||
}
|
||||
#elseif os(macOS)
|
||||
viewBuilder {
|
||||
if readOnly {
|
||||
TextEditor(text: .constant(viewModel.profileContent))
|
||||
} else {
|
||||
TextEditor(text: $viewModel.profileContent)
|
||||
}
|
||||
}
|
||||
.font(Font.system(.caption2, design: .monospaced))
|
||||
.autocorrectionDisabled(true)
|
||||
.textContentType(.init(rawValue: ""))
|
||||
.padding()
|
||||
.onChangeCompat(of: viewModel.profileContent) {
|
||||
viewModel.markAsChanged()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.alertBinding($viewModel.alert)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#if os(iOS)
|
||||
import Runestone
|
||||
import UIKit
|
||||
|
||||
final class ProfileEditorTheme: Theme {
|
||||
let font: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
||||
let textColor: UIColor = .label
|
||||
|
||||
let gutterBackgroundColor: UIColor = .secondarySystemBackground
|
||||
let gutterHairlineColor: UIColor = .separator
|
||||
|
||||
let lineNumberColor: UIColor = .secondaryLabel
|
||||
let lineNumberFont: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
||||
|
||||
let selectedLineBackgroundColor: UIColor = .systemFill
|
||||
let selectedLinesLineNumberColor: UIColor = .label
|
||||
let selectedLinesGutterBackgroundColor: UIColor = .secondarySystemBackground
|
||||
|
||||
let invisibleCharactersColor: UIColor = .tertiaryLabel
|
||||
|
||||
let pageGuideHairlineColor: UIColor = .separator
|
||||
let pageGuideBackgroundColor: UIColor = .secondarySystemBackground
|
||||
|
||||
let markedTextBackgroundColor: UIColor = .systemFill
|
||||
let markedTextBackgroundCornerRadius: CGFloat = 4
|
||||
|
||||
func textColor(for rawHighlightName: String) -> UIColor? {
|
||||
guard let highlightName = HighlightName(rawHighlightName) else {
|
||||
return nil
|
||||
}
|
||||
switch highlightName {
|
||||
case .comment:
|
||||
return .secondaryLabel
|
||||
case .property:
|
||||
return .systemCyan
|
||||
case .string:
|
||||
return .systemGreen
|
||||
case .number:
|
||||
return .systemOrange
|
||||
case .constantBuiltin:
|
||||
return .systemPurple
|
||||
case .error:
|
||||
return .systemRed
|
||||
}
|
||||
}
|
||||
|
||||
func fontTraits(for _: String) -> FontTraits {
|
||||
[]
|
||||
}
|
||||
}
|
||||
|
||||
private enum HighlightName: String {
|
||||
case comment
|
||||
case property
|
||||
case string
|
||||
case number
|
||||
case constantBuiltin = "constant.builtin"
|
||||
case error
|
||||
|
||||
init?(_ rawHighlightName: String) {
|
||||
var components = rawHighlightName.split(separator: ".")
|
||||
while !components.isEmpty {
|
||||
let candidateRawHighlightName = components.joined(separator: ".")
|
||||
if let highlightName = Self(rawValue: candidateRawHighlightName) {
|
||||
self = highlightName
|
||||
return
|
||||
}
|
||||
components.removeLast()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,85 @@
|
||||
#if os(iOS)
|
||||
import Runestone
|
||||
import SwiftUI
|
||||
import TreeSitterJSON5Runestone
|
||||
|
||||
struct RunestoneTextView: UIViewRepresentable {
|
||||
@Binding var text: String
|
||||
let isEditable: Bool
|
||||
|
||||
func makeUIView(context: Context) -> TextView {
|
||||
let textView = TextView()
|
||||
|
||||
textView.showLineNumbers = true
|
||||
textView.isLineWrappingEnabled = false
|
||||
textView.showTabs = false
|
||||
textView.showSpaces = false
|
||||
textView.showLineBreaks = false
|
||||
textView.showSoftLineBreaks = false
|
||||
textView.showNonBreakingSpaces = false
|
||||
|
||||
textView.autocorrectionType = .no
|
||||
textView.autocapitalizationType = .none
|
||||
textView.smartDashesType = .no
|
||||
textView.smartQuotesType = .no
|
||||
textView.smartInsertDeleteType = .no
|
||||
|
||||
textView.backgroundColor = .secondarySystemGroupedBackground
|
||||
textView.contentInsetAdjustmentBehavior = .always
|
||||
textView.alwaysBounceVertical = true
|
||||
|
||||
textView.kern = 0.3
|
||||
textView.lineHeightMultiplier = 1.3
|
||||
|
||||
textView.characterPairs = [
|
||||
BasicCharacterPair(leading: "{", trailing: "}"),
|
||||
BasicCharacterPair(leading: "[", trailing: "]"),
|
||||
BasicCharacterPair(leading: "\"", trailing: "\""),
|
||||
]
|
||||
|
||||
let theme = ProfileEditorTheme()
|
||||
let state = TextViewState(text: text, theme: theme, language: .json5)
|
||||
textView.setState(state)
|
||||
|
||||
textView.isEditable = isEditable
|
||||
textView.editorDelegate = context.coordinator
|
||||
|
||||
return textView
|
||||
}
|
||||
|
||||
func updateUIView(_ textView: TextView, context _: Context) {
|
||||
if textView.text != text {
|
||||
textView.text = text
|
||||
}
|
||||
if textView.isEditable != isEditable {
|
||||
textView.isEditable = isEditable
|
||||
}
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
final class Coordinator: TextViewDelegate {
|
||||
var parent: RunestoneTextView
|
||||
|
||||
init(_ parent: RunestoneTextView) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func textViewDidChange(_ textView: TextView) {
|
||||
parent.text = textView.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class BasicCharacterPair: CharacterPair {
|
||||
let leading: String
|
||||
let trailing: String
|
||||
|
||||
init(leading: String, trailing: String) {
|
||||
self.leading = leading
|
||||
self.trailing = trailing
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user