Compare commits

...

4 Commits

11 changed files with 615 additions and 15 deletions

View File

@ -40,6 +40,11 @@
D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF3F23C839010012D658 /* QueryViewController.xib */; }; D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF3F23C839010012D658 /* QueryViewController.xib */; };
D63CDF4423C970C50012D658 /* DatabaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF4223C970C50012D658 /* DatabaseViewController.swift */; }; D63CDF4423C970C50012D658 /* DatabaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CDF4223C970C50012D658 /* DatabaseViewController.swift */; };
D63CDF4523C970C50012D658 /* DatabaseViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D63CDF4323C970C50012D658 /* DatabaseViewController.xib */; }; 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 */; }; D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -63,6 +68,15 @@
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
D6D13AFF2436C33D00493D97 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -93,6 +107,11 @@
D63CDF3F23C839010012D658 /* QueryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QueryViewController.xib; sourceTree = "<group>"; }; D63CDF3F23C839010012D658 /* QueryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QueryViewController.xib; sourceTree = "<group>"; };
D63CDF4223C970C50012D658 /* DatabaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseViewController.swift; sourceTree = "<group>"; }; D63CDF4223C970C50012D658 /* DatabaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseViewController.swift; sourceTree = "<group>"; };
D63CDF4323C970C50012D658 /* DatabaseViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DatabaseViewController.xib; sourceTree = "<group>"; }; D63CDF4323C970C50012D658 /* DatabaseViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DatabaseViewController.xib; sourceTree = "<group>"; };
D6A7D095243541A400B46857 /* WindowStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowStatusView.swift; sourceTree = "<group>"; };
D6A7D099243546B500B46857 /* WindowStatusView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WindowStatusView.xib; sourceTree = "<group>"; };
D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptHighlighter.swift; sourceTree = "<group>"; };
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 = "<group>"; };
D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MongoEvaluator.swift; sourceTree = "<group>"; }; D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MongoEvaluator.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -113,6 +132,13 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
D6D13AFE2436C33D00493D97 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
@ -123,6 +149,8 @@
D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */, D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */,
D63CDF3A23C838470012D658 /* DatabaseWindowController.swift */, D63CDF3A23C838470012D658 /* DatabaseWindowController.swift */,
D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */, D63CDF3B23C838470012D658 /* DatabaseWindowController.xib */,
D6A7D095243541A400B46857 /* WindowStatusView.swift */,
D6A7D099243546B500B46857 /* WindowStatusView.xib */,
); );
path = Windows; path = Windows;
sourceTree = "<group>"; sourceTree = "<group>";
@ -144,6 +172,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D63CDEBC23C837DC0012D658 /* MongoView */, D63CDEBC23C837DC0012D658 /* MongoView */,
D6D13B022436C33D00493D97 /* jstest */,
D63CDEBB23C837DC0012D658 /* Products */, D63CDEBB23C837DC0012D658 /* Products */,
D63CDF1523C837F10012D658 /* Frameworks */, D63CDF1523C837F10012D658 /* Frameworks */,
); );
@ -153,6 +182,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D63CDEBA23C837DC0012D658 /* MongoView.app */, D63CDEBA23C837DC0012D658 /* MongoView.app */,
D6D13B012436C33D00493D97 /* jstest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -164,6 +194,7 @@
D63CDF3423C838190012D658 /* MongoController.swift */, D63CDF3423C838190012D658 /* MongoController.swift */,
D63CDF3323C838190012D658 /* Node.swift */, D63CDF3323C838190012D658 /* Node.swift */,
D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */, D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */,
D6A7D0A22435880700B46857 /* Synax Highlighting */,
D60C863B23CA2DD600C9DB8E /* Windows */, D60C863B23CA2DD600C9DB8E /* Windows */,
D60C863C23CA2DDD00C9DB8E /* View Controllers */, D60C863C23CA2DDD00C9DB8E /* View Controllers */,
D63CDEBF23C837DD0012D658 /* Assets.xcassets */, D63CDEBF23C837DD0012D658 /* Assets.xcassets */,
@ -190,6 +221,22 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D6A7D0A22435880700B46857 /* Synax Highlighting */ = {
isa = PBXGroup;
children = (
D6A7D0A32435885B00B46857 /* JavaScriptHighlighter.swift */,
);
path = "Synax Highlighting";
sourceTree = "<group>";
};
D6D13B022436C33D00493D97 /* jstest */ = {
isa = PBXGroup;
children = (
D6D13B032436C33D00493D97 /* main.swift */,
);
path = jstest;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -211,19 +258,39 @@
productReference = D63CDEBA23C837DC0012D658 /* MongoView.app */; productReference = D63CDEBA23C837DC0012D658 /* MongoView.app */;
productType = "com.apple.product-type.application"; 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 */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
D63CDEB223C837DC0012D658 /* Project object */ = { D63CDEB223C837DC0012D658 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 1130; LastSwiftUpdateCheck = 1140;
LastUpgradeCheck = 1130; LastUpgradeCheck = 1140;
ORGANIZATIONNAME = Shadowfacts; ORGANIZATIONNAME = Shadowfacts;
TargetAttributes = { TargetAttributes = {
D63CDEB923C837DC0012D658 = { D63CDEB923C837DC0012D658 = {
CreatedOnToolsVersion = 11.3; CreatedOnToolsVersion = 11.3;
}; };
D6D13B002436C33D00493D97 = {
CreatedOnToolsVersion = 11.4;
};
}; };
}; };
buildConfigurationList = D63CDEB523C837DC0012D658 /* Build configuration list for PBXProject "MongoView" */; buildConfigurationList = D63CDEB523C837DC0012D658 /* Build configuration list for PBXProject "MongoView" */;
@ -240,6 +307,7 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
D63CDEB923C837DC0012D658 /* MongoView */, D63CDEB923C837DC0012D658 /* MongoView */,
D6D13B002436C33D00493D97 /* jstest */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -253,6 +321,7 @@
D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */, D63CDEC023C837DD0012D658 /* Assets.xcassets in Resources */,
D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */, D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */,
D63CDF4523C970C50012D658 /* DatabaseViewController.xib in Resources */, D63CDF4523C970C50012D658 /* DatabaseViewController.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 */,
D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */, D63CDF4123C839010012D658 /* QueryViewController.xib in Resources */,
@ -274,10 +343,21 @@
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 */,
D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */,
D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */, D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
D6D13AFD2436C33D00493D97 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D6D13B082436C34200493D97 /* JavaScriptHighlighter.swift in Sources */,
D6D13B042436C33D00493D97 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
@ -410,17 +490,18 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = MongoView/MongoView.entitlements; CODE_SIGN_ENTITLEMENTS = MongoView/MongoView.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = HGYVAQA9FW; DEVELOPMENT_TEAM = V4WK9KR9U2;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = MongoView/Info.plist; INFOPLIST_FILE = MongoView/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.MongoView; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.MongoView;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@ -431,17 +512,40 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = MongoView/MongoView.entitlements; CODE_SIGN_ENTITLEMENTS = MongoView/MongoView.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = HGYVAQA9FW; DEVELOPMENT_TEAM = V4WK9KR9U2;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = MongoView/Info.plist; INFOPLIST_FILE = MongoView/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.MongoView; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.MongoView;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
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)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@ -468,6 +572,15 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
D6D13B072436C33D00493D97 /* Build configuration list for PBXNativeTarget "jstest" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D6D13B052436C33D00493D97 /* Debug */,
D6D13B062436C33D00493D97 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
}; };
rootObject = D63CDEB223C837DC0012D658 /* Project object */; rootObject = D63CDEB223C837DC0012D658 /* Project object */;

View File

@ -16,6 +16,13 @@ class MongoController {
var group: EventLoopGroup! var group: EventLoopGroup!
var client: MongoClient! var client: MongoClient!
var status: Status = .connecting {
didSet {
statusDidChange.forEach { $0(status) }
}
}
var statusDidChange = [(Status) -> Void]()
init(connectionString: String) { init(connectionString: String) {
self.connectionString = connectionString self.connectionString = connectionString
@ -31,7 +38,13 @@ class MongoController {
group = MultiThreadedEventLoopGroup(numberOfThreads: 1) group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
client = try! MongoClient(connectionString, using: group) do {
client = try MongoClient(connectionString, using: group)
status = .success
} catch {
status = .failed
print("Failed to connect to Mongo: \(error)")
}
} }
func db(for collection: DatabaseCollection) -> MongoDatabase { func db(for collection: DatabaseCollection) -> MongoDatabase {
@ -43,3 +56,9 @@ class MongoController {
} }
} }
extension MongoController {
enum Status {
case connecting, success, failed
}
}

View File

@ -9,7 +9,9 @@
import Foundation import Foundation
import MongoSwift import MongoSwift
class Node { // Node needs to be an NSObject, since NSOutlineView uses NSObject.isEqual(_:) and NSObject.hash to determine item equality
// which is necessary to prevent items from collapsing when refreshing the view
class Node: NSObject {
let key: Key? let key: Key?
let value: BSON let value: BSON
weak var parent: Node? weak var parent: Node?
@ -57,10 +59,22 @@ class Node {
self.init(key: nil, value: .document(document)) self.init(key: nil, value: .document(document))
} }
} }
override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Node else { return false }
return self.parent == object.parent && self.key == object.key
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(parent)
hasher.combine(key)
return hasher.finalize()
}
} }
extension Node { extension Node {
enum Key { enum Key: Equatable, Hashable {
case index(Int) case index(Int)
case name(String) case name(String)
case objectId(ObjectId) case objectId(ObjectId)

View File

@ -0,0 +1,269 @@
//
// 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..<to, in: text)
}
private func peek() -> Unicode.Scalar {
return text.unicodeScalars[currentIndex]
}
private func peek(length: Int) -> Substring {
let realLength = min(length, text.distance(from: currentIndex, to: text.endIndex))
return text[currentIndex..<text.index(currentIndex, offsetBy: realLength)]
}
@discardableResult
private func consume() -> 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() {
let char = peek()
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 currentIndex < text.endIndex && identifiers.contains(peek()) {
consume()
}
print("Identifier: '\(text[identifierStart..<currentIndex])'")
}
private func consumeNumber() {
let numberStart = currentIndex!
while currentIndex < text.endIndex && CharacterSet.decimalDigits.contains(peek()) {
consume()
}
if currentIndex < text.endIndex && peek() == "." {
consume()
while currentIndex < text.endIndex && CharacterSet.decimalDigits.contains(peek()) {
consume()
}
}
print("Number: \(text[numberStart..<currentIndex])")
attributed.addAttribute(.foregroundColor, value: NSColor.systemBlue, range: range(from: numberStart, to: currentIndex))
}
private func consumeString() {
let stringStart = currentIndex!
let startChar = consume()
while currentIndex < text.endIndex && peek() != startChar && (currentIndex == text.startIndex || text[text.index(before: currentIndex)] != "\\") {
consume()
}
if currentIndex < text.endIndex {
consume() // string closing quote
}
print("String: \(text[stringStart..<currentIndex])")
attributed.addAttribute(.foregroundColor, value: NSColor.systemRed, range: range(from: stringStart, to: currentIndex))
}
private func consumeTemplateString() {
var stringFragmentStart: String.Index? = currentIndex
consume() // opening `
while currentIndex < text.endIndex {
if peek(length: 2) == "${" {
consume() // $
consume() // {
print("Template string fragment: '\(text[stringFragmentStart!..<currentIndex])'")
attributed.addAttribute(.foregroundColor, value: NSColor.systemRed, range: range(from: stringFragmentStart!, to: currentIndex))
consumeTemplateStringExpression()
stringFragmentStart = currentIndex
if currentIndex < text.endIndex && peek() == "}" {
consume()
}
} else if peek() == "`" {
stringFragmentStart = stringFragmentStart ?? currentIndex
consume() // `
print("Template string fragment: '\(text[stringFragmentStart!..<currentIndex])'")
attributed.addAttribute(.foregroundColor, value: NSColor.systemRed, range: range(from: stringFragmentStart!, to: currentIndex))
stringFragmentStart = nil
break
} else {
consume()
}
}
if let start = stringFragmentStart {
print("Template string fragment: '\(text[start..<currentIndex])'")
attributed.addAttribute(.foregroundColor, value: NSColor.systemRed, range: range(from: start, to: currentIndex))
}
}
private func consumeTemplateStringExpression() {
indent += " "
while currentIndex < text.endIndex && peek() != "}" {
consumeExpression()
}
indent = String(indent.dropLast(2))
}
private func consumeOperator() {
print("Operator: \(peek())")
consume()
}
private func consumeFunctionCallOrGrouping() {
consume() // (
print("Opening (")
attributed.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range(from: text.index(before: currentIndex), to: currentIndex))
indent += " "
while currentIndex < text.endIndex && peek() != ")" {
consumeExpression()
}
indent = String(indent.dropLast(2))
if currentIndex < text.endIndex {
consume() // )
print("Closing )")
attributed.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range(from: text.index(before: currentIndex), to: currentIndex))
}
}
private func consumeObject() {
consume() // {
print("Opening {")
attributed.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range(from: text.index(before: currentIndex), to: currentIndex))
indent += " "
object:
while currentIndex < text.endIndex && peek() != "}" {
consumeObjectKey()
if peek() == ":" {
consume() // :
while currentIndex < text.endIndex && peek() != "," && peek() != "}" {
consumeExpression()
}
if currentIndex < text.endIndex {
break object
}
} else if peek() == "," {
continue
} else {
break
}
}
indent = String(indent.dropLast(2))
if currentIndex < text.endIndex {
consume() // }
print("Closing }")
attributed.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range(from: text.index(before: currentIndex), to: currentIndex))
}
}
private func consumeObjectKey() {
let keyStart = currentIndex!
while currentIndex < text.endIndex && identifiers.contains(peek()) {
consume()
}
print("Object key: '\(text[keyStart..<currentIndex])'")
}
private func consumeDotLookup() {
consume() // .
print("Dot lookup")
attributed.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range(from: text.index(before: currentIndex), to: currentIndex))
}
private func consumeArray() {
consume() // [
print("Opening [")
attributed.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range(from: text.index(before: currentIndex), to: currentIndex))
indent += " "
array:
while currentIndex < text.endIndex && peek() != "]" {
print("Array element")
while currentIndex < text.endIndex {
if peek() == "," {
consume() // ,
break
} else if peek() == "]" {
break array
} else {
indent += " "
consumeExpression()
indent = String(indent.dropLast(2))
}
}
}
indent = String(indent.dropLast(2))
if currentIndex < text.endIndex {
consume() // ]
print("Closing ]")
attributed.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range(from: text.index(before: currentIndex), to: currentIndex))
}
}
}

View File

@ -160,7 +160,7 @@ class DatabaseViewController: NSViewController {
extension DatabaseViewController: NSMenuItemValidation { extension DatabaseViewController: NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
if menuItem.action == #selector(runQuery(_:)) { if menuItem.action == #selector(runQuery(_:)) || menuItem.action == #selector(refresh(_:)) {
return queryViewController != nil return queryViewController != nil
} }
return true return true

View File

@ -53,6 +53,8 @@ class QueryViewController: NSViewController {
queryTextView.font = .monospacedSystemFont(ofSize: 13, weight: .regular) queryTextView.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
queryTextView.isAutomaticQuoteSubstitutionEnabled = false queryTextView.isAutomaticQuoteSubstitutionEnabled = false
queryTextView.string = defaultQuery queryTextView.string = defaultQuery
queryTextView.delegate = self
highlightQuery()
outlineView.dataSource = self outlineView.dataSource = self
outlineView.delegate = self outlineView.delegate = self
@ -76,6 +78,10 @@ class QueryViewController: NSViewController {
view.window!.makeFirstResponder(outlineView) view.window!.makeFirstResponder(outlineView)
} }
func highlightQuery() {
_ = JavaScriptHighlighter(text: queryTextView.string).highlight(mutableAttributed: queryTextView.textStorage!)
}
func refresh(reload: Bool = true) { func refresh(reload: Bool = true) {
if let query = mostRecentQuery { if let query = mostRecentQuery {
let connStr = "\(mongoController.connectionString)/\(collection.database)" let connStr = "\(mongoController.connectionString)/\(collection.database)"
@ -170,6 +176,12 @@ class QueryViewController: NSViewController {
} }
extension QueryViewController: NSTextViewDelegate {
func textDidChange(_ notification: Notification) {
highlightQuery()
}
}
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(_:)) {

View File

@ -10,6 +10,10 @@ import Cocoa
class DatabaseWindowController: NSWindowController { class DatabaseWindowController: NSWindowController {
@IBOutlet weak var toolbar: NSToolbar!
weak var statusView: WindowStatusView?
private var titleObservation: NSKeyValueObservation? private var titleObservation: NSKeyValueObservation?
var mongoController: MongoController! var mongoController: MongoController!
@ -28,6 +32,11 @@ class DatabaseWindowController: NSWindowController {
mongoController = MongoController(connectionString: "mongodb://localhost:27017") mongoController = MongoController(connectionString: "mongodb://localhost:27017")
mongoController.setup() mongoController.setup()
} }
mongoController.statusDidChange.append({ [weak self] (status) in
guard let self = self else { return }
self.updateStatusText(status)
})
self.updateStatusText(mongoController.status)
databaseViewController = DatabaseViewController(mongoController: mongoController) databaseViewController = DatabaseViewController(mongoController: mongoController)
contentViewController = databaseViewController contentViewController = databaseViewController
@ -43,6 +52,7 @@ class DatabaseWindowController: NSWindowController {
window!.tabbingMode = .preferred window!.tabbingMode = .preferred
window!.tabbingIdentifier = mongoController.connectionString window!.tabbingIdentifier = mongoController.connectionString
window!.titleVisibility = .hidden
} }
override func newWindowForTab(_ sender: Any?) { override func newWindowForTab(_ sender: Any?) {
@ -52,5 +62,73 @@ class DatabaseWindowController: NSWindowController {
private func updateWindowTitle() { private func updateWindowTitle() {
window?.title = databaseViewController.title ?? "MongoView" window?.title = databaseViewController.title ?? "MongoView"
} }
private func updateStatusText(_ status: MongoController.Status) {
guard let statusView = self.statusView else { return }
switch status {
case .connecting:
statusView.setText("Connecting...")
case .failed:
statusView.setText("Failed to connect")
case .success:
statusView.setText("Connected to \(self.mongoController.connectionString)")
}
}
} }
extension NSToolbarItem.Identifier {
static let mongoStatus = NSToolbarItem.Identifier("MongoView.mongoStatus")
static let refresh = NSToolbarItem.Identifier("MongoView.refresh")
}
extension DatabaseWindowController: NSToolbarDelegate {
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [
.space,
.flexibleSpace,
.mongoStatus,
.refresh,
]
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [
.refresh,
.flexibleSpace,
.mongoStatus,
.flexibleSpace,
]
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
if itemIdentifier == .mongoStatus {
return createMongoStatusToolbarItem()
} else if itemIdentifier == .refresh {
return createRefreshToolbarItem()
}
return NSToolbarItem(itemIdentifier: itemIdentifier)
}
func createMongoStatusToolbarItem() -> NSToolbarItem {
let item = NSToolbarItem(itemIdentifier: .mongoStatus)
item.label = "Status"
item.paletteLabel = "Status"
let statusView = WindowStatusView.create()
self.statusView = statusView
item.view = statusView
return item
}
func createRefreshToolbarItem() -> NSToolbarItem {
let item = NSToolbarItem(itemIdentifier: .refresh)
item.label = "Refresh"
item.paletteLabel = "Refresh"
item.target = self
let button = NSButton(image: NSImage(named: NSImage.refreshTemplateName)!, target: nil, action: #selector(DatabaseViewController.refresh(_:)))
button.bezelStyle = .texturedRounded
item.view = button
return item
}
}

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15702" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15702"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<customObject id="-2" userLabel="File's Owner" customClass="DatabaseWindowController" customModule="MongoView" customModuleProvider="target"> <customObject id="-2" userLabel="File's Owner" customClass="DatabaseWindowController" customModule="MongoView" customModuleProvider="target">
<connections> <connections>
<outlet property="toolbar" destination="zlC-gF-itI" id="Blh-Q7-7Ie"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/> <outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections> </connections>
</customObject> </customObject>
@ -19,8 +20,15 @@
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/> <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="se5-gp-TjO"> <view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/> <rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view> </view>
<toolbar key="toolbar" implicitIdentifier="E8476463-3A21-4F21-99C1-EEBBCC89844B" displayMode="iconOnly" sizeMode="regular" id="zlC-gF-itI">
<allowedToolbarItems/>
<defaultToolbarItems/>
<connections>
<outlet property="delegate" destination="-2" id="zRR-2c-mGv"/>
</connections>
</toolbar>
<contentBorderThickness minY="22"/> <contentBorderThickness minY="22"/>
<connections> <connections>
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/> <outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>

View File

@ -0,0 +1,32 @@
//
// WindowStatusView.swift
// MongoView
//
// Created by Shadowfacts on 4/1/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Cocoa
class WindowStatusView: NSView {
@IBOutlet weak var button: NSButton!
static func create() -> WindowStatusView {
let nib = NSNib(nibNamed: "WindowStatusView", bundle: .main)
var objects: NSArray? = NSArray()
nib?.instantiate(withOwner: nil, topLevelObjects: &objects)
return objects!.first(where: { $0 is WindowStatusView }) as! WindowStatusView
}
override func awakeFromNib() {
super.awakeFromNib()
(button.cell as! NSButtonCell).imageDimsWhenDisabled = false
}
func setText(_ text: String) {
button.title = text
}
}

View File

@ -0,0 +1,42 @@
<?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"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="c22-O7-iKe" customClass="WindowStatusView" customModule="MongoView" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="500" height="22"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rXx-fM-BhC">
<rect key="frame" x="0.0" y="-1" width="500" height="23"/>
<constraints>
<constraint firstAttribute="width" constant="500" id="ScX-uS-BYM"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Textured Rounded" bezelStyle="texturedRounded" image="NSAdvanced" imagePosition="trailing" alignment="left" lineBreakMode="truncatingTail" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="dLk-x4-UWx">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="rXx-fM-BhC" secondAttribute="bottom" id="8WA-ZA-Q0D"/>
<constraint firstItem="rXx-fM-BhC" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" id="QSy-IQ-7qC"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="TUy-Ci-y94"/>
<constraint firstAttribute="trailing" secondItem="rXx-fM-BhC" secondAttribute="trailing" id="ksp-LZ-zjw"/>
<constraint firstItem="rXx-fM-BhC" firstAttribute="top" secondItem="c22-O7-iKe" secondAttribute="top" id="owa-mM-nje"/>
<constraint firstAttribute="height" constant="22" id="qfB-JT-wOX"/>
</constraints>
<connections>
<outlet property="button" destination="rXx-fM-BhC" id="25d-1p-Awz"/>
</connections>
<point key="canvasLocation" x="140" y="154"/>
</customView>
</objects>
<resources>
<image name="NSAdvanced" width="32" height="32"/>
</resources>
</document>

13
jstest/main.swift Normal file
View File

@ -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()