From 56efc109d762e1dd06b14934d85b9b3898ef9bed Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 2 Apr 2020 22:59:05 -0400 Subject: [PATCH] Add rudimentary JavaScript highlighting to query text view --- MongoView.xcodeproj/project.pbxproj | 105 ++++++- .../JavaScriptHighlighter.swift | 276 ++++++++++++++++++ .../QueryViewController.swift | 12 + jstest/main.swift | 13 + 4 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 MongoView/Synax Highlighting/JavaScriptHighlighter.swift create mode 100644 jstest/main.swift diff --git a/MongoView.xcodeproj/project.pbxproj b/MongoView.xcodeproj/project.pbxproj index 6055219..e258f72 100644 --- a/MongoView.xcodeproj/project.pbxproj +++ b/MongoView.xcodeproj/project.pbxproj @@ -42,6 +42,9 @@ D63CDF4523C970C50012D658 /* DatabaseViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF4323C970C50012D658 /* DatabaseViewController.xib */; }; D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D095243541A400B46857 /* WindowStatusView.swift */; }; D6A7D09A243546B500B46857 /* WindowStatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A7D099243546B500B46857 /* WindowStatusView.xib */; }; + D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */; }; + D6D13B042436C33D00493D97 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D13B032436C33D00493D97 /* main.swift */; }; + D6D13B082436C34200493D97 /* JavaScriptHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */; }; D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */; }; /* End PBXBuildFile section */ @@ -65,6 +68,15 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D6D13AFF2436C33D00493D97 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -97,6 +109,9 @@ D63CDF4323C970C50012D658 /* DatabaseViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DatabaseViewController.xib; sourceTree = ""; }; D6A7D095243541A400B46857 /* WindowStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowStatusView.swift; sourceTree = ""; }; D6A7D099243546B500B46857 /* WindowStatusView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WindowStatusView.xib; sourceTree = ""; }; + D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptHighlighter.swift; sourceTree = ""; }; + D6D13B012436C33D00493D97 /* jstest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jstest; sourceTree = BUILT_PRODUCTS_DIR; }; + D6D13B032436C33D00493D97 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MongoEvaluator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -117,6 +132,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D6D13AFE2436C33D00493D97 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -150,6 +172,7 @@ isa = PBXGroup; children = ( D63CDEBC23C837DC0012D658 /* MongoView */, + D6D13B022436C33D00493D97 /* jstest */, D63CDEBB23C837DC0012D658 /* Products */, D63CDF1523C837F10012D658 /* Frameworks */, ); @@ -159,6 +182,7 @@ isa = PBXGroup; children = ( D63CDEBA23C837DC0012D658 /* MongoView.app */, + D6D13B012436C33D00493D97 /* jstest */, ); name = Products; sourceTree = ""; @@ -170,6 +194,7 @@ D63CDF3423C838190012D658 /* MongoController.swift */, D63CDF3323C838190012D658 /* Node.swift */, D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */, + D6A7D0A22435880700B46857 /* Synax Highlighting */, D60C863B23CA2DD600C9DB8E /* Windows */, D60C863C23CA2DDD00C9DB8E /* View Controllers */, D63CDEBF23C837DD0012D658 /* Assets.xcassets */, @@ -196,6 +221,22 @@ name = Frameworks; sourceTree = ""; }; + D6A7D0A22435880700B46857 /* Synax Highlighting */ = { + isa = PBXGroup; + children = ( + D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */, + ); + path = "Synax Highlighting"; + sourceTree = ""; + }; + D6D13B022436C33D00493D97 /* jstest */ = { + isa = PBXGroup; + children = ( + D6D13B032436C33D00493D97 /* main.swift */, + ); + path = jstest; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -217,19 +258,39 @@ productReference = D63CDEBA23C837DC0012D658 /* MongoView.app */; productType = "com.apple.product-type.application"; }; + D6D13B002436C33D00493D97 /* jstest */ = { + isa = PBXNativeTarget; + buildConfigurationList = D6D13B072436C33D00493D97 /* Build configuration list for PBXNativeTarget "jstest" */; + buildPhases = ( + D6D13AFD2436C33D00493D97 /* Sources */, + D6D13AFE2436C33D00493D97 /* Frameworks */, + D6D13AFF2436C33D00493D97 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = jstest; + productName = jstest; + productReference = D6D13B012436C33D00493D97 /* jstest */; + productType = "com.apple.product-type.tool"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ D63CDEB223C837DC0012D658 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1130; + LastSwiftUpdateCheck = 1140; LastUpgradeCheck = 1140; ORGANIZATIONNAME = Shadowfacts; TargetAttributes = { D63CDEB923C837DC0012D658 = { CreatedOnToolsVersion = 11.3; }; + D6D13B002436C33D00493D97 = { + CreatedOnToolsVersion = 11.4; + }; }; }; buildConfigurationList = D63CDEB523C837DC0012D658 /* Build configuration list for PBXProject "MongoView" */; @@ -246,6 +307,7 @@ projectRoot = ""; targets = ( D63CDEB923C837DC0012D658 /* MongoView */, + D6D13B002436C33D00493D97 /* jstest */, ); }; /* End PBXProject section */ @@ -282,10 +344,20 @@ D63CDF4423C970C50012D658 /* DatabaseViewController.swift in Sources */, D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */, D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */, + D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */, D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + D6D13AFD2436C33D00493D97 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D6D13B082436C34200493D97 /* JavaScriptHighlighter.swift in Sources */, + D6D13B042436C33D00493D97 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ @@ -457,6 +529,28 @@ }; name = Release; }; + D6D13B052436C33D00493D97 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HGYVAQA9FW; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D6D13B062436C33D00493D97 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HGYVAQA9FW; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -478,6 +572,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D6D13B072436C33D00493D97 /* Build configuration list for PBXNativeTarget "jstest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D6D13B052436C33D00493D97 /* Debug */, + D6D13B062436C33D00493D97 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = D63CDEB223C837DC0012D658 /* Project object */; diff --git a/MongoView/Synax Highlighting/JavaScriptHighlighter.swift b/MongoView/Synax Highlighting/JavaScriptHighlighter.swift new file mode 100644 index 0000000..4cf31c9 --- /dev/null +++ b/MongoView/Synax Highlighting/JavaScriptHighlighter.swift @@ -0,0 +1,276 @@ +// +// 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 consumeExpression() { + 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 consumeIdentifier() { + let identifierStart = currentIndex! + while let char = peek(), identifiers.contains(char) { + consume() + } + print("Identifier: '\(text[identifierStart.. Bool { if menuItem.action == #selector(deleteNode(_:)) { diff --git a/jstest/main.swift b/jstest/main.swift new file mode 100644 index 0000000..09c2796 --- /dev/null +++ b/jstest/main.swift @@ -0,0 +1,13 @@ +// +// main.swift +// jstest +// +// Created by Shadowfacts on 4/2/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import Foundation + +let source = "`+`${{a:3}}`" +_ = JavaScriptHighlighter(text: source).highlight() +