// // JavaScriptHighlighter.swift // MongoView // // Created by Shadowfacts on 4/1/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import AppKit fileprivate let identifiers: CharacterSet = { var set = CharacterSet.alphanumerics set.insert(charactersIn: "$_") return set }() fileprivate let identifierStarts: CharacterSet = { var set = identifiers set.subtract(.decimalDigits) return set }() fileprivate let operators = CharacterSet(charactersIn: "+-*/<>=") class JavaScriptHighlighter { private let text: String private var attributed: NSMutableAttributedString! private var currentIndex: String.Index! private var indent = "" init(text: String) { self.text = text } private func print(_ str: String) { Swift.print("\(indent)\(str)") } private func range(from: String.Index, to: String.Index) -> NSRange { return NSRange(from.. NSRange { return range(from: text.index(before: currentIndex), to: currentIndex) } private func peek() -> Unicode.Scalar? { guard currentIndex < text.endIndex else { return nil } return text.unicodeScalars[currentIndex] } private func peek(length: Int) -> Substring { let realLength = min(length, text.distance(from: currentIndex, to: text.endIndex)) return text[currentIndex.. Unicode.Scalar? { let c = peek() currentIndex = text.index(after: currentIndex) return c } func highlight(mutableAttributed: NSMutableAttributedString? = nil) -> NSAttributedString { attributed = mutableAttributed ?? NSMutableAttributedString(attributedString: NSAttributedString(string: text)) let fullRange = NSRange(location: 0, length: attributed.length) attributed.setAttributes([ .foregroundColor: NSColor.textColor, .font: NSFont.monospacedSystemFont(ofSize: 13, weight: .regular) ], range: fullRange) currentIndex = text.startIndex while currentIndex < text.endIndex { consumeExpression() } return attributed } private func emit(token: TokenType, range: NSRange) { let color: NSColor switch token { case .string: color = .systemRed case .number: color = .systemBlue case .punctuation: color = .systemTeal case .identifier: return } attributed.addAttribute(.foregroundColor, value: color, range: range) } private func consumeExpression() { consumeWhitespace() guard let char = peek() else { return } if identifierStarts.contains(char) { consumeIdentifier() } else if char == "'" || char == "\"" { consumeString() } else if char == "`" { consumeTemplateString() } else if CharacterSet.decimalDigits.contains(char) { consumeNumber() } else if operators.contains(char) { consumeOperator() } else if char == "(" { consumeFunctionCallOrGrouping() } else if char == "{" { consumeObject() } else if char == "[" { consumeArray() } else if char == "." { consumeDotLookup() } else { consume() } } private func consumeWhitespace(newlines: Bool = false) { let charSet = newlines ? CharacterSet.whitespacesAndNewlines : .whitespaces while let char = peek(), charSet.contains(char) { consume() } } private func consumeIdentifier() { let identifierStart = currentIndex! while let char = peek(), identifiers.contains(char) { consume() } print("Identifier: '\(text[identifierStart..