aboutsummaryrefslogtreecommitdiffstats
path: root/Sources/WireGuardApp/UI/macOS/View/ConfTextStorage.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Sources/WireGuardApp/UI/macOS/View/ConfTextStorage.swift')
-rw-r--r--Sources/WireGuardApp/UI/macOS/View/ConfTextStorage.swift196
1 files changed, 196 insertions, 0 deletions
diff --git a/Sources/WireGuardApp/UI/macOS/View/ConfTextStorage.swift b/Sources/WireGuardApp/UI/macOS/View/ConfTextStorage.swift
new file mode 100644
index 0000000..574859d
--- /dev/null
+++ b/Sources/WireGuardApp/UI/macOS/View/ConfTextStorage.swift
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
+
+import Cocoa
+
+private let fontSize: CGFloat = 15
+
+class ConfTextStorage: NSTextStorage {
+ let defaultFont = NSFontManager.shared.convertWeight(true, of: NSFont.systemFont(ofSize: fontSize))
+ private let boldFont = NSFont.boldSystemFont(ofSize: fontSize)
+ private lazy var italicFont = NSFontManager.shared.convert(defaultFont, toHaveTrait: .italicFontMask)
+
+ private var textColorTheme: ConfTextColorTheme.Type?
+
+ private let backingStore: NSMutableAttributedString
+ private(set) var hasError = false
+ private(set) var privateKeyString: String?
+
+ private(set) var hasOnePeer = false
+ private(set) var lastOnePeerAllowedIPs = [String]()
+ private(set) var lastOnePeerDNSServers = [String]()
+ private(set) var lastOnePeerHasPublicKey = false
+
+ override init() {
+ backingStore = NSMutableAttributedString(string: "")
+ super.init()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ required init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) {
+ fatalError("init(pasteboardPropertyList:ofType:) has not been implemented")
+ }
+
+ func nonColorAttributes(for highlightType: highlight_type) -> [NSAttributedString.Key: Any] {
+ switch highlightType.rawValue {
+ case HighlightSection.rawValue, HighlightField.rawValue:
+ return [.font: boldFont]
+ case HighlightPublicKey.rawValue, HighlightPrivateKey.rawValue, HighlightPresharedKey.rawValue,
+ HighlightIP.rawValue, HighlightCidr.rawValue, HighlightHost.rawValue, HighlightPort.rawValue,
+ HighlightMTU.rawValue, HighlightKeepalive.rawValue, HighlightDelimiter.rawValue:
+ return [.font: defaultFont]
+ case HighlightComment.rawValue:
+ return [.font: italicFont]
+ case HighlightError.rawValue:
+ return [.font: defaultFont, .underlineStyle: 1]
+ default:
+ return [:]
+ }
+ }
+
+ func updateAttributes(for textColorTheme: ConfTextColorTheme.Type) {
+ self.textColorTheme = textColorTheme
+ highlightSyntax()
+ }
+
+ override var string: String {
+ return backingStore.string
+ }
+
+ override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key: Any] {
+ return backingStore.attributes(at: location, effectiveRange: range)
+ }
+
+ override func replaceCharacters(in range: NSRange, with str: String) {
+ beginEditing()
+ backingStore.replaceCharacters(in: range, with: str)
+ edited(.editedCharacters, range: range, changeInLength: str.utf16.count - range.length)
+ endEditing()
+ }
+
+ override func replaceCharacters(in range: NSRange, with attrString: NSAttributedString) {
+ beginEditing()
+ backingStore.replaceCharacters(in: range, with: attrString)
+ edited(.editedCharacters, range: range, changeInLength: attrString.length - range.length)
+ endEditing()
+ }
+
+ override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
+ beginEditing()
+ backingStore.setAttributes(attrs, range: range)
+ edited(.editedAttributes, range: range, changeInLength: 0)
+ endEditing()
+ }
+
+ func resetLastPeer() {
+ hasOnePeer = false
+ lastOnePeerAllowedIPs = []
+ lastOnePeerDNSServers = []
+ lastOnePeerHasPublicKey = false
+ }
+
+ func evaluateExcludePrivateIPs(highlightSpans: UnsafePointer<highlight_span>) {
+ var spans = highlightSpans
+ let string = backingStore.string
+ enum FieldType: String {
+ case dns
+ case allowedips
+ }
+ var fieldType: FieldType?
+ resetLastPeer()
+ while spans.pointee.type != HighlightEnd {
+ let span = spans.pointee
+ var substring = String(string.substring(higlightSpan: span)).lowercased()
+
+ if span.type == HighlightError {
+ resetLastPeer()
+ return
+ } else if span.type == HighlightSection {
+ if substring == "[peer]" {
+ if hasOnePeer {
+ resetLastPeer()
+ return
+ }
+ hasOnePeer = true
+ }
+ } else if span.type == HighlightField {
+ fieldType = FieldType(rawValue: substring)
+ } else if span.type == HighlightIP && fieldType == .dns {
+ lastOnePeerDNSServers.append(substring)
+ } else if span.type == HighlightIP && fieldType == .allowedips {
+ let next = spans.successor()
+ let nextnext = next.successor()
+ if next.pointee.type == HighlightDelimiter && nextnext.pointee.type == HighlightCidr {
+ let delimiter = string.substring(higlightSpan: next.pointee)
+ let cidr = string.substring(higlightSpan: nextnext.pointee)
+ substring += delimiter + cidr
+ }
+ lastOnePeerAllowedIPs.append(substring)
+ } else if span.type == HighlightPublicKey {
+ lastOnePeerHasPublicKey = true
+ }
+ spans = spans.successor()
+ }
+ }
+
+ func highlightSyntax() {
+ guard let textColorTheme = textColorTheme else { return }
+ hasError = false
+ privateKeyString = nil
+
+ let string = backingStore.string
+ let fullTextRange = NSRange(..<string.endIndex, in: string)
+
+ backingStore.beginEditing()
+ let defaultAttributes: [NSAttributedString.Key: Any] = [
+ .foregroundColor: textColorTheme.defaultColor,
+ .font: defaultFont
+ ]
+ backingStore.setAttributes(defaultAttributes, range: fullTextRange)
+ var spans = highlight_config(string)!
+ evaluateExcludePrivateIPs(highlightSpans: spans)
+
+ let spansStart = spans
+ while spans.pointee.type != HighlightEnd {
+ let span = spans.pointee
+
+ let startIndex = string.utf8.index(string.startIndex, offsetBy: span.start)
+ let endIndex = string.utf8.index(startIndex, offsetBy: span.len)
+ let range = NSRange(startIndex..<endIndex, in: string)
+
+ backingStore.setAttributes(nonColorAttributes(for: span.type), range: range)
+
+ let color = textColorTheme.colorMap[span.type.rawValue, default: textColorTheme.defaultColor]
+ backingStore.addAttribute(.foregroundColor, value: color, range: range)
+
+ if span.type == HighlightError {
+ hasError = true
+ }
+
+ if span.type == HighlightPrivateKey {
+ privateKeyString = String(string.substring(higlightSpan: span))
+ }
+
+ spans = spans.successor()
+ }
+ backingStore.endEditing()
+ free(spansStart)
+
+ beginEditing()
+ edited(.editedAttributes, range: fullTextRange, changeInLength: 0)
+ endEditing()
+ }
+
+}
+
+private extension String {
+ func substring(higlightSpan span: highlight_span) -> Substring {
+ let startIndex = self.utf8.index(self.utf8.startIndex, offsetBy: span.start)
+ let endIndex = self.utf8.index(startIndex, offsetBy: span.len)
+
+ return self[startIndex..<endIndex]
+ }
+}