Compare commits
8 Commits
83cfba74a8
...
6b39e90ea4
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 6b39e90ea4 | |
Shadowfacts | f30675169a | |
Shadowfacts | d353fabd69 | |
Shadowfacts | c89d6d5f39 | |
Shadowfacts | 33b1159848 | |
Shadowfacts | cc9e21be16 | |
Shadowfacts | 7b090bcb4a | |
Shadowfacts | 7b54a18bcc |
|
@ -13,6 +13,10 @@
|
||||||
D60C864023CA2E2100C9DB8E /* ServerConnectViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C863E23CA2E2100C9DB8E /* ServerConnectViewController.xib */; };
|
D60C864023CA2E2100C9DB8E /* ServerConnectViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C863E23CA2E2100C9DB8E /* ServerConnectViewController.xib */; };
|
||||||
D62408C12438CF550020E09F /* JavaScriptEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62408C02438CF550020E09F /* JavaScriptEditorView.swift */; };
|
D62408C12438CF550020E09F /* JavaScriptEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62408C02438CF550020E09F /* JavaScriptEditorView.swift */; };
|
||||||
D624090F243903E90020E09F /* ExtendedJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D624090E243903E90020E09F /* ExtendedJSON.swift */; };
|
D624090F243903E90020E09F /* ExtendedJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D624090E243903E90020E09F /* ExtendedJSON.swift */; };
|
||||||
|
D626BF82243BD2EE0075117B /* EditDocumentWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626BF80243BD2EE0075117B /* EditDocumentWindowController.swift */; };
|
||||||
|
D626BF83243BD2EE0075117B /* EditDocumentWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626BF81243BD2EE0075117B /* EditDocumentWindowController.xib */; };
|
||||||
|
D626BF86243BE19A0075117B /* EditDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626BF84243BE19A0075117B /* EditDocumentViewController.swift */; };
|
||||||
|
D626BF87243BE19A0075117B /* EditDocumentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626BF85243BE19A0075117B /* EditDocumentViewController.xib */; };
|
||||||
D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDEBD23C837DC0012D658 /* AppDelegate.swift */; };
|
D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDEBD23C837DC0012D658 /* AppDelegate.swift */; };
|
||||||
D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEBF23C837DD0012D658 /* Assets.xcassets */; };
|
D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEBF23C837DD0012D658 /* Assets.xcassets */; };
|
||||||
D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEC123C837DD0012D658 /* MainMenu.xib */; };
|
D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDEC123C837DD0012D658 /* MainMenu.xib */; };
|
||||||
|
@ -88,6 +92,10 @@
|
||||||
D60C863E23CA2E2100C9DB8E /* ServerConnectViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ServerConnectViewController.xib; sourceTree = "<group>"; };
|
D60C863E23CA2E2100C9DB8E /* ServerConnectViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ServerConnectViewController.xib; sourceTree = "<group>"; };
|
||||||
D62408C02438CF550020E09F /* JavaScriptEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptEditorView.swift; sourceTree = "<group>"; };
|
D62408C02438CF550020E09F /* JavaScriptEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptEditorView.swift; sourceTree = "<group>"; };
|
||||||
D624090E243903E90020E09F /* ExtendedJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedJSON.swift; sourceTree = "<group>"; };
|
D624090E243903E90020E09F /* ExtendedJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedJSON.swift; sourceTree = "<group>"; };
|
||||||
|
D626BF80243BD2EE0075117B /* EditDocumentWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditDocumentWindowController.swift; sourceTree = "<group>"; };
|
||||||
|
D626BF81243BD2EE0075117B /* EditDocumentWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditDocumentWindowController.xib; sourceTree = "<group>"; };
|
||||||
|
D626BF84243BE19A0075117B /* EditDocumentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditDocumentViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D626BF85243BE19A0075117B /* EditDocumentViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditDocumentViewController.xib; sourceTree = "<group>"; };
|
||||||
D63CDEBA23C837DC0012D658 /* MongoView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MongoView.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D63CDEBA23C837DC0012D658 /* MongoView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MongoView.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D63CDEBD23C837DC0012D658 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
D63CDEBD23C837DC0012D658 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
D63CDEBF23C837DD0012D658 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
D63CDEBF23C837DD0012D658 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
@ -153,6 +161,8 @@
|
||||||
D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */,
|
D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */,
|
||||||
D63CDF3A23C838470012D658 /* DatabaseWindowController.swift */,
|
D63CDF3A23C838470012D658 /* DatabaseWindowController.swift */,
|
||||||
D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */,
|
D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */,
|
||||||
|
D626BF80243BD2EE0075117B /* EditDocumentWindowController.swift */,
|
||||||
|
D626BF81243BD2EE0075117B /* EditDocumentWindowController.xib */,
|
||||||
);
|
);
|
||||||
path = Windows;
|
path = Windows;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -166,6 +176,8 @@
|
||||||
D63CDF4323C970C50012D658 /* DatabaseViewController.xib */,
|
D63CDF4323C970C50012D658 /* DatabaseViewController.xib */,
|
||||||
D63CDF3E23C839010012D658 /* QueryViewController.swift */,
|
D63CDF3E23C839010012D658 /* QueryViewController.swift */,
|
||||||
D63CDF3F23C839010012D658 /* QueryViewController.xib */,
|
D63CDF3F23C839010012D658 /* QueryViewController.xib */,
|
||||||
|
D626BF84243BE19A0075117B /* EditDocumentViewController.swift */,
|
||||||
|
D626BF85243BE19A0075117B /* EditDocumentViewController.xib */,
|
||||||
);
|
);
|
||||||
path = "View Controllers";
|
path = "View Controllers";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -334,10 +346,12 @@
|
||||||
D60C864023CA2E2100C9DB8E /* ServerConnectViewController.xib in Resources */,
|
D60C864023CA2E2100C9DB8E /* ServerConnectViewController.xib in Resources */,
|
||||||
D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */,
|
D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */,
|
||||||
D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */,
|
D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */,
|
||||||
|
D626BF87243BE19A0075117B /* EditDocumentViewController.xib in Resources */,
|
||||||
D63CDF4523C970C50012D658 /* DatabaseViewController.xib in Resources */,
|
D63CDF4523C970C50012D658 /* DatabaseViewController.xib in Resources */,
|
||||||
D6A7D09A243546B500B46857 /* WindowStatusView.xib in Resources */,
|
D6A7D09A243546B500B46857 /* WindowStatusView.xib in Resources */,
|
||||||
D63CDF3D23C838470012D658 /* DatabaseWindowController.xib in Resources */,
|
D63CDF3D23C838470012D658 /* DatabaseWindowController.xib in Resources */,
|
||||||
D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */,
|
D63CDEC323C837DD0012D658 /* MainMenu.xib in Resources */,
|
||||||
|
D626BF83243BD2EE0075117B /* EditDocumentWindowController.xib in Resources */,
|
||||||
D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */,
|
D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -355,10 +369,12 @@
|
||||||
D63CDF3823C8381A0012D658 /* MongoController.swift in Sources */,
|
D63CDF3823C8381A0012D658 /* MongoController.swift in Sources */,
|
||||||
D60C863923CA2DD100C9DB8E /* ServerConnectWindowController.swift in Sources */,
|
D60C863923CA2DD100C9DB8E /* ServerConnectWindowController.swift in Sources */,
|
||||||
D63CDF3723C8381A0012D658 /* Node.swift in Sources */,
|
D63CDF3723C8381A0012D658 /* Node.swift in Sources */,
|
||||||
|
D626BF86243BE19A0075117B /* EditDocumentViewController.swift in Sources */,
|
||||||
D60C863F23CA2E2100C9DB8E /* ServerConnectViewController.swift in Sources */,
|
D60C863F23CA2E2100C9DB8E /* ServerConnectViewController.swift in Sources */,
|
||||||
D63CDF4423C970C50012D658 /* DatabaseViewController.swift in Sources */,
|
D63CDF4423C970C50012D658 /* DatabaseViewController.swift in Sources */,
|
||||||
D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */,
|
D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */,
|
||||||
D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */,
|
D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */,
|
||||||
|
D626BF82243BD2EE0075117B /* EditDocumentWindowController.swift in Sources */,
|
||||||
D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */,
|
D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */,
|
||||||
D62408C12438CF550020E09F /* JavaScriptEditorView.swift in Sources */,
|
D62408C12438CF550020E09F /* JavaScriptEditorView.swift in Sources */,
|
||||||
D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */,
|
D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1140"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D63CDEB923C837DC0012D658"
|
||||||
|
BuildableName = "MongoView.app"
|
||||||
|
BlueprintName = "MongoView"
|
||||||
|
ReferencedContainer = "container:MongoView.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D63CDEB923C837DC0012D658"
|
||||||
|
BuildableName = "MongoView.app"
|
||||||
|
BlueprintName = "MongoView"
|
||||||
|
ReferencedContainer = "container:MongoView.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D63CDEB923C837DC0012D658"
|
||||||
|
BuildableName = "MongoView.app"
|
||||||
|
BlueprintName = "MongoView"
|
||||||
|
ReferencedContainer = "container:MongoView.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1140"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D6D13B002436C33D00493D97"
|
||||||
|
BuildableName = "jstest"
|
||||||
|
BlueprintName = "jstest"
|
||||||
|
ReferencedContainer = "container:MongoView.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D6D13B002436C33D00493D97"
|
||||||
|
BuildableName = "jstest"
|
||||||
|
BlueprintName = "jstest"
|
||||||
|
ReferencedContainer = "container:MongoView.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D6D13B002436C33D00493D97"
|
||||||
|
BuildableName = "jstest"
|
||||||
|
BlueprintName = "jstest"
|
||||||
|
ReferencedContainer = "container:MongoView.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JavaScriptCore
|
import JavaScriptCore
|
||||||
|
import MongoSwift
|
||||||
|
|
||||||
struct ExtendedJSON {
|
struct ExtendedJSON {
|
||||||
private init() {}
|
private init() {}
|
||||||
|
@ -25,4 +26,27 @@ struct ExtendedJSON {
|
||||||
return context.evaluateScript("JSON.stringify(\(string))")?.toString()
|
return context.evaluateScript("JSON.stringify(\(string))")?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func fromExtJSON(_ string: String) -> Document? {
|
||||||
|
do {
|
||||||
|
let doc = try Document(fromJSON: string)
|
||||||
|
return doc
|
||||||
|
} catch {
|
||||||
|
print("Unable to create document from extended JSON: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func toDocument(_ string: String) -> Document? {
|
||||||
|
guard let normalized = normalize(string),
|
||||||
|
let doc = fromExtJSON(normalized) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
|
||||||
|
static func prettify(_ string: String) -> String? {
|
||||||
|
let command = "JSON.stringify(JSON.parse(`\(string)`), null, 4)"
|
||||||
|
return context.evaluateScript(command)?.toString()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import OSLog
|
||||||
|
|
||||||
|
fileprivate let log = OSLog(subsystem: "space.vaccor.MongoView.JavaScriptHighlighter", category: .pointsOfInterest)
|
||||||
|
|
||||||
fileprivate let identifiers: CharacterSet = {
|
fileprivate let identifiers: CharacterSet = {
|
||||||
var set = CharacterSet.alphanumerics
|
var set = CharacterSet.alphanumerics
|
||||||
|
@ -22,24 +25,30 @@ fileprivate let operators = CharacterSet(charactersIn: "+-*/<>=")
|
||||||
fileprivate let expressionEnds = CharacterSet(charactersIn: ",]});")
|
fileprivate let expressionEnds = CharacterSet(charactersIn: ",]});")
|
||||||
|
|
||||||
class JavaScriptHighlighter {
|
class JavaScriptHighlighter {
|
||||||
private let text: String
|
private var text: String!
|
||||||
private var attributed: NSMutableAttributedString
|
private var attributed: NSMutableAttributedString!
|
||||||
private var currentIndex: String.Index!
|
private var currentIndex: String.Index!
|
||||||
private var indent = ""
|
private var _indent = ""
|
||||||
private(set) var tokens = [(token: TokenType, range: NSRange)]()
|
private(set) var tokens = [(token: TokenType, range: NSRange)]()
|
||||||
|
var debug = false
|
||||||
|
|
||||||
init(text: String) {
|
private func print(_ str: @autoclosure () -> String) {
|
||||||
self.text = text
|
#if DEBUG
|
||||||
self.attributed = NSMutableAttributedString(attributedString: NSAttributedString(string: text))
|
if debug {
|
||||||
|
Swift.print("\(_indent)\(str())")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
init(mutableAttributed: NSMutableAttributedString) {
|
private func indent() {
|
||||||
self.text = mutableAttributed.string
|
#if DEBUG
|
||||||
self.attributed = mutableAttributed
|
_indent += " "
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
private func outdent() {
|
||||||
private func print(_ str: String) {
|
#if DEBUG
|
||||||
Swift.print("\(indent)\(str)")
|
_indent = String(_indent.dropLast(2))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func range(from: String.Index, to: String.Index) -> NSRange {
|
private func range(from: String.Index, to: String.Index) -> NSRange {
|
||||||
|
@ -62,14 +71,29 @@ class JavaScriptHighlighter {
|
||||||
return text[currentIndex..<text.index(currentIndex, offsetBy: realLength)]
|
return text[currentIndex..<text.index(currentIndex, offsetBy: realLength)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
private func consume() {
|
||||||
private func consume() -> Unicode.Scalar? {
|
|
||||||
let c = peek()
|
|
||||||
currentIndex = text.index(after: currentIndex)
|
currentIndex = text.index(after: currentIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getAndConsume() -> Unicode.Scalar? {
|
||||||
|
let c = peek()
|
||||||
|
consume()
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func highlight() {
|
func highlight(text: String) {
|
||||||
|
self.highlight(attributed: NSMutableAttributedString(attributedString: NSAttributedString(string: text)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func highlight(attributed: NSMutableAttributedString) {
|
||||||
|
self.attributed = attributed
|
||||||
|
self.text = attributed.string
|
||||||
|
self.tokens = []
|
||||||
|
|
||||||
|
os_signpost(.begin, log: log, name: "highlight")
|
||||||
|
defer { os_signpost(.end, log: log, name: "highlight") }
|
||||||
|
|
||||||
|
attributed.beginEditing()
|
||||||
let fullRange = NSRange(location: 0, length: attributed.length)
|
let fullRange = NSRange(location: 0, length: attributed.length)
|
||||||
attributed.setAttributes([
|
attributed.setAttributes([
|
||||||
.foregroundColor: NSColor.textColor,
|
.foregroundColor: NSColor.textColor,
|
||||||
|
@ -80,21 +104,99 @@ class JavaScriptHighlighter {
|
||||||
while let char = peek(), !expressionEnds.contains(char) {
|
while let char = peek(), !expressionEnds.contains(char) {
|
||||||
consumeExpression()
|
consumeExpression()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attributed.endEditing()
|
||||||
|
}
|
||||||
|
|
||||||
|
func inserted(character: Unicode.Scalar, index: Int) -> Bool {
|
||||||
|
os_signpost(.begin, log: log, name: "inserted(character:index:)")
|
||||||
|
defer { os_signpost(.end, log: log, name: "inserted(character:index:)") }
|
||||||
|
|
||||||
|
if let (token, range) = tokenAndRange(fullyContaining: index) {
|
||||||
|
switch token {
|
||||||
|
case .identifier:
|
||||||
|
let set = range.location == index ? identifierStarts : identifiers
|
||||||
|
guard set.contains(character) else { return false }
|
||||||
|
case let .string(stringChar):
|
||||||
|
guard character != stringChar || (index > 0 && Unicode.Scalar((text as NSString).character(at: index - 1)) == "\\"),
|
||||||
|
character != "\\" else { return false }
|
||||||
|
case .number:
|
||||||
|
if character == "." && (attributed.string as NSString).substring(with: range).contains(Character(".")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var attributes: [NSAttributedString.Key: Any] = [
|
||||||
|
.font: NSFont.monospacedSystemFont(ofSize: 13, weight: .regular)
|
||||||
|
]
|
||||||
|
if let color = token.color {
|
||||||
|
attributes[.foregroundColor] = color
|
||||||
|
}
|
||||||
|
attributed.addAttributes(attributes, range: NSRange(location: index, length: 1))
|
||||||
|
tokens = tokens.map { (token, range) in
|
||||||
|
if range.contains(index) {
|
||||||
|
return (token, NSRange(location: range.location, length: range.length + 1))
|
||||||
|
} else if range.location >= index {
|
||||||
|
return (token, NSRange(location: range.location + 1, length: range.length))
|
||||||
|
} else {
|
||||||
|
return (token, range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func removed(index: Int) -> Bool {
|
||||||
|
os_signpost(.begin, log: log, name: "removed(at:)")
|
||||||
|
defer { os_signpost(.end, log: log, name: "removed(at:)") }
|
||||||
|
|
||||||
|
if let (token, range) = tokenAndRange(fullyContaining: index) {
|
||||||
|
switch token {
|
||||||
|
case .string(_):
|
||||||
|
guard index > range.location && index < range.location + range.length - 1 else { return false }
|
||||||
|
if index > 0 && Unicode.Scalar((text as NSString).character(at: index - 1)) == "\\" {
|
||||||
|
// removed character
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .number:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tokens = tokens.map { (token, range) in
|
||||||
|
if range.contains(index) {
|
||||||
|
return (token, NSRange(location: range.location, length: range.length - 1))
|
||||||
|
} else if range.location >= index {
|
||||||
|
return (token, NSRange(location: range.location - 1, length: range.length))
|
||||||
|
} else {
|
||||||
|
return (token, range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func token(at index: Int) -> TokenType? {
|
||||||
|
for (token, range) in tokens where range.contains(index) {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tokenAndRange(fullyContaining index: Int) -> (TokenType, NSRange)? {
|
||||||
|
for (token, range) in tokens where index > range.location && index < range.location + range.length {
|
||||||
|
return (token, range)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func emit(token: TokenType, range: NSRange) {
|
private func emit(token: TokenType, range: NSRange) {
|
||||||
let color: NSColor
|
if let color = token.color {
|
||||||
switch token {
|
|
||||||
case .string:
|
|
||||||
color = .systemRed
|
|
||||||
case .number:
|
|
||||||
color = .systemBlue
|
|
||||||
case .punctuation:
|
|
||||||
color = .systemTeal
|
|
||||||
case .identifier:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
attributed.addAttribute(.foregroundColor, value: color, range: range)
|
attributed.addAttribute(.foregroundColor, value: color, range: range)
|
||||||
|
}
|
||||||
tokens.append((token, range))
|
tokens.append((token, range))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +225,8 @@ class JavaScriptHighlighter {
|
||||||
consumeDotLookup()
|
consumeDotLookup()
|
||||||
} else if char == "?" {
|
} else if char == "?" {
|
||||||
consumeTernaryExpression()
|
consumeTernaryExpression()
|
||||||
|
} else if expressionEnds.contains(char) {
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
consume()
|
consume()
|
||||||
}
|
}
|
||||||
|
@ -162,15 +266,20 @@ class JavaScriptHighlighter {
|
||||||
|
|
||||||
private func consumeString() {
|
private func consumeString() {
|
||||||
let stringStart = currentIndex!
|
let stringStart = currentIndex!
|
||||||
let startChar = consume()
|
let quote = peek()!
|
||||||
while currentIndex < text.endIndex && peek() != startChar && (currentIndex == text.startIndex || text[text.index(before: currentIndex)] != "\\") {
|
consume() // opening quote
|
||||||
|
var prevChar: Unicode.Scalar?
|
||||||
|
var char = peek()
|
||||||
|
while currentIndex < text.endIndex {
|
||||||
consume()
|
consume()
|
||||||
|
if char == quote && prevChar != "\\" {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if currentIndex < text.endIndex {
|
prevChar = char
|
||||||
consume() // string closing quote
|
char = peek()
|
||||||
}
|
}
|
||||||
print("String: \(text[stringStart..<currentIndex])")
|
print("String: \(text[stringStart..<currentIndex])")
|
||||||
emit(token: .string, range: range(from: stringStart, to: currentIndex))
|
emit(token: .string(quote), range: range(from: stringStart, to: currentIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func consumeTemplateString() {
|
private func consumeTemplateString() {
|
||||||
|
@ -179,14 +288,14 @@ class JavaScriptHighlighter {
|
||||||
func emitTemplateStringFragment() {
|
func emitTemplateStringFragment() {
|
||||||
guard stringFragmentStart != currentIndex else { return }
|
guard stringFragmentStart != currentIndex else { return }
|
||||||
print("Template string fragment: '\(text[stringFragmentStart!..<currentIndex])'")
|
print("Template string fragment: '\(text[stringFragmentStart!..<currentIndex])'")
|
||||||
emit(token: .string, range: range(from: stringFragmentStart!, to: currentIndex))
|
emit(token: .string("`"), range: range(from: stringFragmentStart!, to: currentIndex))
|
||||||
stringFragmentStart = currentIndex
|
stringFragmentStart = currentIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
consume() // opening `
|
consume() // opening `
|
||||||
|
|
||||||
while currentIndex < text.endIndex {
|
while let char = peek() {
|
||||||
if peek() == "$" {
|
if char == "$" {
|
||||||
emitTemplateStringFragment()
|
emitTemplateStringFragment()
|
||||||
consume() // $
|
consume() // $
|
||||||
if peek() == "{" {
|
if peek() == "{" {
|
||||||
|
@ -199,7 +308,7 @@ class JavaScriptHighlighter {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if peek() == "`" {
|
} else if char == "`" {
|
||||||
stringFragmentStart = stringFragmentStart ?? currentIndex
|
stringFragmentStart = stringFragmentStart ?? currentIndex
|
||||||
consume() // `
|
consume() // `
|
||||||
emitTemplateStringFragment()
|
emitTemplateStringFragment()
|
||||||
|
@ -215,11 +324,11 @@ class JavaScriptHighlighter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func consumeTemplateStringExpression() {
|
private func consumeTemplateStringExpression() {
|
||||||
indent += " "
|
indent()
|
||||||
while currentIndex < text.endIndex && peek() != "}" {
|
while currentIndex < text.endIndex && peek() != "}" {
|
||||||
consumeExpression()
|
consumeExpression()
|
||||||
}
|
}
|
||||||
indent = String(indent.dropLast(2))
|
outdent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func consumeOperator() {
|
private func consumeOperator() {
|
||||||
|
@ -231,11 +340,11 @@ class JavaScriptHighlighter {
|
||||||
consume() // (
|
consume() // (
|
||||||
print("Opening (")
|
print("Opening (")
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
indent += " "
|
indent()
|
||||||
while currentIndex < text.endIndex && peek() != ")" {
|
while currentIndex < text.endIndex && peek() != ")" {
|
||||||
consumeExpression()
|
consumeExpression()
|
||||||
}
|
}
|
||||||
indent = String(indent.dropLast(2))
|
outdent()
|
||||||
if currentIndex < text.endIndex {
|
if currentIndex < text.endIndex {
|
||||||
consume() // )
|
consume() // )
|
||||||
print("Closing )")
|
print("Closing )")
|
||||||
|
@ -247,35 +356,30 @@ class JavaScriptHighlighter {
|
||||||
consume() // {
|
consume() // {
|
||||||
print("Opening {")
|
print("Opening {")
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
indent += " "
|
indent()
|
||||||
object:
|
while let char = peek() {
|
||||||
while currentIndex < text.endIndex && peek() != "}" {
|
if char == "}" {
|
||||||
consumeObjectKey()
|
|
||||||
if peek() == ":" {
|
|
||||||
consume() // :
|
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
|
||||||
consumeWhitespace()
|
|
||||||
while currentIndex < text.endIndex && peek() != "," && peek() != "}" {
|
|
||||||
consumeExpression()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
consumeWhitespace()
|
|
||||||
|
|
||||||
if peek() == "," {
|
|
||||||
consume() // ,
|
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
indent = String(indent.dropLast(2))
|
|
||||||
if currentIndex < text.endIndex {
|
|
||||||
consume() // }
|
consume() // }
|
||||||
print("Closing }")
|
print("Closing }")
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
|
break
|
||||||
|
} else if char == "'" || char == "\"" {
|
||||||
|
let keyStart = currentIndex!
|
||||||
|
consumeString()
|
||||||
|
print("Object key: '\(text[keyStart..<currentIndex])'")
|
||||||
|
} else if identifierStarts.contains(char) {
|
||||||
|
let keyStart = currentIndex!
|
||||||
|
consumeIdentifier()
|
||||||
|
print("Object key: '\(text[keyStart..<currentIndex])'")
|
||||||
|
} else if char == ":" || char == "," {
|
||||||
|
consume() // :
|
||||||
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
|
} else {
|
||||||
|
consumeExpression()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
outdent()
|
||||||
|
}
|
||||||
|
|
||||||
private func consumeObjectKey() {
|
private func consumeObjectKey() {
|
||||||
consumeWhitespace()
|
consumeWhitespace()
|
||||||
|
@ -292,46 +396,47 @@ class JavaScriptHighlighter {
|
||||||
|
|
||||||
private func consumeDotLookup() {
|
private func consumeDotLookup() {
|
||||||
consume() // .
|
consume() // .
|
||||||
|
guard let char = peek() else { return }
|
||||||
|
if identifierStarts.contains(char) {
|
||||||
print("Dot lookup")
|
print("Dot lookup")
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
|
consumeIdentifier()
|
||||||
|
} else if CharacterSet.decimalDigits.contains(char) {
|
||||||
|
let numberStart = text.index(before: currentIndex)
|
||||||
|
while let char = peek(), CharacterSet.decimalDigits.contains(char) {
|
||||||
|
consume()
|
||||||
|
}
|
||||||
|
print("Number: \(text[numberStart..<currentIndex])")
|
||||||
|
emit(token: .number, range: range(from: numberStart, to: currentIndex))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func consumeArray() {
|
private func consumeArray() {
|
||||||
consume() // [
|
consume() // [
|
||||||
print("Opening [")
|
print("Opening [")
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
indent += " "
|
indent()
|
||||||
array:
|
while let char = peek() {
|
||||||
while currentIndex < text.endIndex && peek() != "]" {
|
if char == "]" {
|
||||||
consumeWhitespace()
|
|
||||||
while currentIndex < text.endIndex {
|
|
||||||
if peek() == "," {
|
|
||||||
consume() // ,
|
|
||||||
print("Array separator")
|
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
|
||||||
break
|
|
||||||
} else if peek() == "]" {
|
|
||||||
break array
|
|
||||||
} else {
|
|
||||||
indent += " "
|
|
||||||
print("Array element")
|
|
||||||
consumeExpression()
|
|
||||||
indent = String(indent.dropLast(2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
indent = String(indent.dropLast(2))
|
|
||||||
if currentIndex < text.endIndex {
|
|
||||||
consume() // ]
|
consume() // ]
|
||||||
print("Closing ]")
|
print("Closing ]")
|
||||||
emit(token: .punctuation, range: prevCharRange())
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
|
break
|
||||||
|
} else if char == "," {
|
||||||
|
consume() // ,
|
||||||
|
print("Array separator")
|
||||||
|
emit(token: .punctuation, range: prevCharRange())
|
||||||
|
} else {
|
||||||
|
consumeExpression()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
outdent()
|
||||||
|
}
|
||||||
|
|
||||||
func consumeTernaryExpression() {
|
func consumeTernaryExpression() {
|
||||||
consume() // ?
|
consume() // ?
|
||||||
print("Ternary expression")
|
print("Ternary expression")
|
||||||
indent += " "
|
indent()
|
||||||
print("Ternary true result")
|
print("Ternary true result")
|
||||||
while let char = peek(), char != ":" {
|
while let char = peek(), char != ":" {
|
||||||
consumeExpression() // true result
|
consumeExpression() // true result
|
||||||
|
@ -341,7 +446,7 @@ class JavaScriptHighlighter {
|
||||||
while let char = peek(), !expressionEnds.contains(char) {
|
while let char = peek(), !expressionEnds.contains(char) {
|
||||||
consumeExpression()
|
consumeExpression()
|
||||||
}
|
}
|
||||||
indent = String(indent.dropLast(2))
|
outdent()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -351,6 +456,19 @@ extension JavaScriptHighlighter {
|
||||||
case identifier
|
case identifier
|
||||||
case punctuation
|
case punctuation
|
||||||
case number
|
case number
|
||||||
case string
|
case string(Unicode.Scalar)
|
||||||
|
|
||||||
|
var color: NSColor? {
|
||||||
|
switch self {
|
||||||
|
case .string(_):
|
||||||
|
return .systemRed
|
||||||
|
case .number:
|
||||||
|
return .systemBlue
|
||||||
|
case .punctuation:
|
||||||
|
return .systemTeal
|
||||||
|
case .identifier:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// EditDocumentViewController.swift
|
||||||
|
// MongoView
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 4/6/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import MongoSwift
|
||||||
|
|
||||||
|
class EditDocumentViewController: NSViewController {
|
||||||
|
|
||||||
|
private(set) var mongoController: MongoController!
|
||||||
|
private(set) var collection: DatabaseCollection!
|
||||||
|
private(set) var document: Document!
|
||||||
|
|
||||||
|
var documentEdited: (() -> Void)?
|
||||||
|
|
||||||
|
@IBOutlet var editorTextView: JavaScriptEditorView!
|
||||||
|
@IBOutlet weak var saveButton: NSButton!
|
||||||
|
@IBOutlet weak var cancelButton: NSButton!
|
||||||
|
@IBOutlet weak var validateButton: NSButton!
|
||||||
|
|
||||||
|
init(mongoController: MongoController, collection: DatabaseCollection, document: Document) {
|
||||||
|
self.mongoController = mongoController
|
||||||
|
self.collection = collection
|
||||||
|
self.document = document
|
||||||
|
|
||||||
|
super.init(nibName: "EditDocumentViewController", bundle: .main)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
editorTextView.string = ExtendedJSON.prettify(document.extendedJSON) ?? document.extendedJSON
|
||||||
|
editorTextView.isAutomaticQuoteSubstitutionEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func savePressed(_ sender: Any) {
|
||||||
|
guard let id = document["_id"],
|
||||||
|
let newDoc = ExtendedJSON.toDocument(editorTextView.string) else {
|
||||||
|
// todo: error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let collection = mongoController.collection(self.collection)
|
||||||
|
collection.replaceOne(filter: ["_id": id], replacement: newDoc).whenComplete { (result) in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
switch result {
|
||||||
|
case .success(_):
|
||||||
|
self.documentEdited?()
|
||||||
|
case let .failure(error):
|
||||||
|
print("Unable to replace document: \(error)")
|
||||||
|
}
|
||||||
|
self.view.window!.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func cancelPressed(_ sender: Any) {
|
||||||
|
view.window!.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func validatePressed(_ sender: Any) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="EditDocumentViewController" customModule="MongoView" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="cancelButton" destination="5M7-Eo-LRQ" id="9iE-dG-Rvz"/>
|
||||||
|
<outlet property="editorTextView" destination="1aa-Vo-yPS" id="hip-Qi-9BH"/>
|
||||||
|
<outlet property="saveButton" destination="3aO-fI-F6G" id="hbc-f1-hAJ"/>
|
||||||
|
<outlet property="validateButton" destination="J9n-en-a5p" id="SoQ-HS-byg"/>
|
||||||
|
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<customView id="Hz6-mo-xeY">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xa0-UE-ywz">
|
||||||
|
<rect key="frame" x="273" y="8" width="199" height="21"/>
|
||||||
|
<subviews>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5M7-Eo-LRQ">
|
||||||
|
<rect key="frame" x="-6" y="-7" width="82" height="32"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="xeo-Kh-gMm">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<string key="keyEquivalent" base64-UTF8="YES">
|
||||||
|
Gw
|
||||||
|
</string>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="cancelPressed:" target="-2" id="bmf-UI-VmZ"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3aO-fI-F6G">
|
||||||
|
<rect key="frame" x="72" y="-7" width="133" height="32"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Save and Close" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="FgS-jM-BWn">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="savePressed:" target="-2" id="Blr-6P-WPb"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<visibilityPriorities>
|
||||||
|
<integer value="1000"/>
|
||||||
|
<integer value="1000"/>
|
||||||
|
</visibilityPriorities>
|
||||||
|
<customSpacing>
|
||||||
|
<real value="3.4028234663852886e+38"/>
|
||||||
|
<real value="3.4028234663852886e+38"/>
|
||||||
|
</customSpacing>
|
||||||
|
</stackView>
|
||||||
|
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MlX-0S-w82">
|
||||||
|
<rect key="frame" x="8" y="37" width="464" height="227"/>
|
||||||
|
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="egN-c7-XBK">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="449" height="227"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" id="1aa-Vo-yPS" customClass="JavaScriptEditorView" customModule="MongoView" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="449" height="227"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<size key="minSize" width="449" height="227"/>
|
||||||
|
<size key="maxSize" width="465" height="10000000"/>
|
||||||
|
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
</clipView>
|
||||||
|
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="IwO-eE-cem">
|
||||||
|
<rect key="frame" x="-100" y="-100" width="225" height="15"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</scroller>
|
||||||
|
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="IDr-mZ-kqd">
|
||||||
|
<rect key="frame" x="449" y="0.0" width="15" height="227"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</scroller>
|
||||||
|
</scrollView>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="J9n-en-a5p">
|
||||||
|
<rect key="frame" x="2" y="1" width="89" height="32"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Validate" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="J6T-UY-zPK">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="validatePressed:" target="-2" id="gVI-wh-rT2"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="MlX-0S-w82" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="8" id="BdG-le-KXB"/>
|
||||||
|
<constraint firstItem="J9n-en-a5p" firstAttribute="top" secondItem="MlX-0S-w82" secondAttribute="bottom" constant="8" id="FvY-Be-Tdx"/>
|
||||||
|
<constraint firstItem="J9n-en-a5p" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="8" id="HzC-6Y-gZG"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="xa0-UE-ywz" secondAttribute="bottom" constant="8" id="Ssc-Oa-bTe"/>
|
||||||
|
<constraint firstItem="xa0-UE-ywz" firstAttribute="top" secondItem="MlX-0S-w82" secondAttribute="bottom" constant="8" id="Uga-ZA-1b4"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="xa0-UE-ywz" secondAttribute="trailing" constant="8" id="i4O-th-zRP"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="J9n-en-a5p" secondAttribute="bottom" constant="8" id="lZs-Qs-8gq"/>
|
||||||
|
<constraint firstItem="MlX-0S-w82" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="8" id="m99-4c-vcx"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="MlX-0S-w82" secondAttribute="trailing" constant="8" id="rYV-TB-qaV"/>
|
||||||
|
</constraints>
|
||||||
|
<point key="canvasLocation" x="140" y="154"/>
|
||||||
|
</customView>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -50,7 +50,6 @@ class QueryViewController: NSViewController {
|
||||||
verticalSplitView.delegate = self
|
verticalSplitView.delegate = self
|
||||||
verticalSplitView.setHoldingPriority(.defaultHigh, forSubviewAt: 0)
|
verticalSplitView.setHoldingPriority(.defaultHigh, forSubviewAt: 0)
|
||||||
|
|
||||||
filterTextView.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
|
|
||||||
filterTextView.isAutomaticQuoteSubstitutionEnabled = false
|
filterTextView.isAutomaticQuoteSubstitutionEnabled = false
|
||||||
filterTextView.string = defaultFilter
|
filterTextView.string = defaultFilter
|
||||||
|
|
||||||
|
@ -80,8 +79,7 @@ class QueryViewController: NSViewController {
|
||||||
let filterText = filterTextView.string.trimmingCharacters(in: .whitespacesAndNewlines)
|
let filterText = filterTextView.string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let filter: Document
|
let filter: Document
|
||||||
if !filterText.isEmpty,
|
if !filterText.isEmpty,
|
||||||
let normalized = ExtendedJSON.normalize(filterText),
|
let doc = ExtendedJSON.toDocument(filterText) {
|
||||||
let doc = try? Document(fromJSON: normalized) {
|
|
||||||
filter = doc
|
filter = doc
|
||||||
} else {
|
} else {
|
||||||
filter = [:]
|
filter = [:]
|
||||||
|
@ -163,11 +161,24 @@ class QueryViewController: NSViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func editDocument(_ sender: Any) {
|
||||||
|
guard let node = outlineView.item(atRow: outlineView.clickedRow) as? Node,
|
||||||
|
node.parent == nil,
|
||||||
|
case let .document(document) = node.value else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let wc = EditDocumentWindowController(mongoController: mongoController, collection: collection, document: document)
|
||||||
|
wc.documentEdited = {
|
||||||
|
self.refresh()
|
||||||
|
}
|
||||||
|
wc.showWindow(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension QueryViewController: NSMenuItemValidation {
|
extension QueryViewController: NSMenuItemValidation {
|
||||||
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
|
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
|
||||||
if menuItem.action == #selector(deleteNode(_:)) {
|
if menuItem.action == #selector(deleteNode(_:)) || menuItem.action == #selector(editDocument(_:)) {
|
||||||
if outlineView.clickedRow != -1, let node = outlineView.item(atRow: outlineView.clickedRow) as? Node, node.parent == nil {
|
if outlineView.clickedRow != -1, let node = outlineView.item(atRow: outlineView.clickedRow) as? Node, node.parent == nil {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1139,6 +1139,12 @@
|
||||||
</customView>
|
</customView>
|
||||||
<menu id="nL5-kg-dty">
|
<menu id="nL5-kg-dty">
|
||||||
<items>
|
<items>
|
||||||
|
<menuItem title="Edit Document" id="0gS-XH-YDt">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="editDocument:" target="-2" id="DP4-Tq-o5M"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
<menuItem title="Delete" id="nn0-ZF-eDZ">
|
<menuItem title="Delete" id="nn0-ZF-eDZ">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
|
|
@ -10,17 +10,30 @@ import AppKit
|
||||||
|
|
||||||
class JavaScriptEditorView: NSTextView {
|
class JavaScriptEditorView: NSTextView {
|
||||||
|
|
||||||
var highlighter: JavaScriptHighlighter?
|
var highlighter = JavaScriptHighlighter()
|
||||||
|
private var isRehighlighting = false
|
||||||
|
|
||||||
override var string: String {
|
override var string: String {
|
||||||
didSet {
|
get {
|
||||||
|
super.string
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
isRehighlighting = true
|
||||||
|
super.string = newValue
|
||||||
rehighlight()
|
rehighlight()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rehighlight() {
|
func rehighlight() {
|
||||||
highlighter = JavaScriptHighlighter(mutableAttributed: self.textStorage!)
|
isRehighlighting = true
|
||||||
highlighter!.highlight()
|
highlighter.highlight(attributed: self.textStorage!)
|
||||||
|
isRehighlighting = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
textStorage!.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
override func shouldChangeText(in affectedCharRange: NSRange, replacementString: String?) -> Bool {
|
override func shouldChangeText(in affectedCharRange: NSRange, replacementString: String?) -> Bool {
|
||||||
|
@ -38,16 +51,11 @@ class JavaScriptEditorView: NSTextView {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didChangeText() {
|
|
||||||
rehighlight()
|
|
||||||
|
|
||||||
super.didChangeText()
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryAutocompleteCharacter(for range: NSRange, string inserted: String) -> Bool {
|
func tryAutocompleteCharacter(for range: NSRange, string inserted: String) -> Bool {
|
||||||
if let end = autocompleteResultFor(string: inserted, in: range) {
|
if let end = autocompleteResultFor(string: inserted, in: range) {
|
||||||
textStorage!.insert(NSAttributedString(string: "\(inserted)\(end)"), at: range.location)
|
textStorage!.insert(NSAttributedString(string: "\(inserted)\(end)"), at: range.location)
|
||||||
didChangeText()
|
rehighlight()
|
||||||
|
super.didChangeText()
|
||||||
setSelectedRange(NSRange(location: range.location + 1, length: 0))
|
setSelectedRange(NSRange(location: range.location + 1, length: 0))
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
@ -58,28 +66,57 @@ class JavaScriptEditorView: NSTextView {
|
||||||
private func autocompleteResultFor(string: String, in range: NSRange) -> String? {
|
private func autocompleteResultFor(string: String, in range: NSRange) -> String? {
|
||||||
switch string {
|
switch string {
|
||||||
case "'", "\"", "`":
|
case "'", "\"", "`":
|
||||||
return token(at: range.location) != .string ? string : nil
|
if case .string(_) = highlighter.token(at: range.location) {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return string
|
||||||
|
}
|
||||||
case "(":
|
case "(":
|
||||||
return token(at: range.location) != .string ? ")" : nil
|
if case .string(_) = highlighter.token(at: range.location) {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return ")"
|
||||||
|
}
|
||||||
case "[":
|
case "[":
|
||||||
return token(at: range.location) != .string ? "]" : nil
|
if case .string(_) = highlighter.token(at: range.location) {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return "]"
|
||||||
|
}
|
||||||
case "{":
|
case "{":
|
||||||
var prevChar: Unicode.Scalar?
|
|
||||||
if range.location > 0 {
|
if range.location > 0 {
|
||||||
let index = self.string.index(self.string.startIndex, offsetBy: range.location - 1)
|
let index = self.string.index(self.string.startIndex, offsetBy: range.location - 1)
|
||||||
prevChar = self.string.unicodeScalars[index]
|
let prevChar = self.string.unicodeScalars[index]
|
||||||
|
if prevChar == "$" {
|
||||||
|
return "}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if case .string(_) = highlighter.token(at: range.location) {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return "}"
|
||||||
}
|
}
|
||||||
return token(at: range.location) != .string || (prevChar == "$") ? "}" : nil
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func token(at index: Int) -> JavaScriptHighlighter.TokenType? {
|
|
||||||
guard let highlighter = highlighter else { return nil }
|
|
||||||
for (token, range) in highlighter.tokens where range.contains(index) {
|
|
||||||
return token
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
extension JavaScriptEditorView: NSTextStorageDelegate {
|
||||||
|
func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
|
||||||
|
guard editedMask.contains(.editedCharacters), !isRehighlighting else { return }
|
||||||
|
if delta == 1 {
|
||||||
|
if let char = Unicode.Scalar((textStorage.string as NSString).character(at: editedRange.location)),
|
||||||
|
highlighter.inserted(character: char, index: editedRange.location) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if delta == -1 {
|
||||||
|
if highlighter.removed(index: editedRange.location) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rehighlight()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// EditDocumentWindowController.swift
|
||||||
|
// MongoView
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 4/6/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import MongoSwift
|
||||||
|
|
||||||
|
class EditDocumentWindowController: NSWindowController {
|
||||||
|
|
||||||
|
private(set) var mongoController: MongoController!
|
||||||
|
private(set) var collection: DatabaseCollection!
|
||||||
|
private(set) var mongoDocument: Document!
|
||||||
|
|
||||||
|
var documentEdited: (() -> Void)?
|
||||||
|
|
||||||
|
convenience init(mongoController: MongoController, collection: DatabaseCollection, document: Document) {
|
||||||
|
self.init(windowNibName: "EditDocumentWindowController")
|
||||||
|
|
||||||
|
self.mongoController = mongoController
|
||||||
|
self.collection = collection
|
||||||
|
self.mongoDocument = document
|
||||||
|
}
|
||||||
|
|
||||||
|
override func windowDidLoad() {
|
||||||
|
super.windowDidLoad()
|
||||||
|
|
||||||
|
window!.title = "Edit \(collection.database).\(collection.name) Document"
|
||||||
|
|
||||||
|
let vc = EditDocumentViewController(mongoController: mongoController, collection: collection, document: mongoDocument)
|
||||||
|
vc.documentEdited = documentEdited
|
||||||
|
contentViewController = vc
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="EditDocumentWindowController" customModule="MongoView" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="documentWindow" id="F0z-JX-Cv5">
|
||||||
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
|
<rect key="contentRect" x="931" y="526" width="1200" height="900"/>
|
||||||
|
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||||
|
<value key="minSize" type="size" width="900" height="600"/>
|
||||||
|
<view key="contentView" id="se5-gp-TjO">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="1200" height="900"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="140" y="147"/>
|
||||||
|
</window>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
let source = "`$foo ${blah} bar`"
|
let source = "[1 + 2, 3,]"
|
||||||
_ = JavaScriptHighlighter(text: source).highlight()
|
let highlighter = JavaScriptHighlighter(text: source)
|
||||||
|
highlighter.debug = true
|
||||||
|
highlighter.highlight()
|
||||||
|
|
Loading…
Reference in New Issue