Compare commits

..

No commits in common. "83cfba74a83fa530b3b81f7f7ef8b9cec9b4003c" and "5b9f59ec098fe31ed4fcd511067ae5c8610164d4" have entirely different histories.

11 changed files with 86 additions and 224 deletions

View File

@ -11,8 +11,6 @@
D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */; }; D60C863A23CA2DD100C9DB8E /* ServerConnectWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */; };
D60C863F23CA2E2100C9DB8E /* ServerConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60C863D23CA2E2100C9DB8E /* ServerConnectViewController.swift */; }; D60C863F23CA2E2100C9DB8E /* ServerConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60C863D23CA2E2100C9DB8E /* ServerConnectViewController.swift */; };
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 */; };
D624090F243903E90020E09F /* ExtendedJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D624090E243903E90020E09F /* ExtendedJSON.swift */; };
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 */; };
@ -86,8 +84,6 @@
D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ServerConnectWindowController.xib; sourceTree = "<group>"; }; D60C863823CA2DD100C9DB8E /* ServerConnectWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ServerConnectWindowController.xib; sourceTree = "<group>"; };
D60C863D23CA2E2100C9DB8E /* ServerConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConnectViewController.swift; sourceTree = "<group>"; }; D60C863D23CA2E2100C9DB8E /* ServerConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConnectViewController.swift; sourceTree = "<group>"; };
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>"; };
D624090E243903E90020E09F /* ExtendedJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedJSON.swift; 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 +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>";
@ -170,16 +168,6 @@
path = "View Controllers"; path = "View Controllers";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D62408BF2438CF3C0020E09F /* Views */ = {
isa = PBXGroup;
children = (
D6A7D095243541A400B46857 /* WindowStatusView.swift */,
D6A7D099243546B500B46857 /* WindowStatusView.xib */,
D62408C02438CF550020E09F /* JavaScriptEditorView.swift */,
);
path = Views;
sourceTree = "<group>";
};
D63CDEB123C837DC0012D658 = { D63CDEB123C837DC0012D658 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -206,11 +194,9 @@
D63CDF3423C838190012D658 /* MongoController.swift */, D63CDF3423C838190012D658 /* MongoController.swift */,
D63CDF3323C838190012D658 /* Node.swift */, D63CDF3323C838190012D658 /* Node.swift */,
D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */, D6D4665223CB730C00F13B1B /* MongoEvaluator.swift */,
D624090E243903E90020E09F /* ExtendedJSON.swift */,
D6A7D0A22435880700B46857 /* Synax Highlighting */, D6A7D0A22435880700B46857 /* Synax Highlighting */,
D60C863B23CA2DD600C9DB8E /* Windows */, D60C863B23CA2DD600C9DB8E /* Windows */,
D60C863C23CA2DDD00C9DB8E /* View Controllers */, D60C863C23CA2DDD00C9DB8E /* View Controllers */,
D62408BF2438CF3C0020E09F /* Views */,
D63CDEBF23C837DD0012D658 /* Assets.xcassets */, D63CDEBF23C837DD0012D658 /* Assets.xcassets */,
D63CDEC123C837DD0012D658 /* MainMenu.xib */, D63CDEC123C837DD0012D658 /* MainMenu.xib */,
D63CDEC423C837DD0012D658 /* Info.plist */, D63CDEC423C837DD0012D658 /* Info.plist */,
@ -351,7 +337,6 @@
files = ( files = (
D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */, D63CDEBE23C837DC0012D658 /* AppDelegate.swift in Sources */,
D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */, D6D4665323CB730C00F13B1B /* MongoEvaluator.swift in Sources */,
D624090F243903E90020E09F /* ExtendedJSON.swift in Sources */,
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 */,
@ -360,7 +345,6 @@
D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */, D63CDF3C23C838470012D658 /* DatabaseWindowController.swift in Sources */,
D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */, D6A7D096243541A400B46857 /* WindowStatusView.swift in Sources */,
D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */, D6A7D0A42435885B00B46857 /* JavaScriptHighlighter.swift in Sources */,
D62408C12438CF550020E09F /* JavaScriptEditorView.swift in Sources */,
D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */, D63CDF4023C839010012D658 /* QueryViewController.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@ -1,7 +1,7 @@
<?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="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16085" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16085"/>
</dependencies> </dependencies>
<objects> <objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -368,6 +368,14 @@
<action selector="refresh:" target="-1" id="S5L-sX-Z2j"/> <action selector="refresh:" target="-1" id="S5L-sX-Z2j"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Run Query" id="RSP-nH-a1I">
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<connections>
<action selector="runQuery:" target="-1" id="jLo-qu-WjN"/>
</connections>
</menuItem>
</items> </items>
</menu> </menu>
</menuItem> </menuItem>

View File

@ -1,28 +0,0 @@
//
// ExtendedJSON.swift
// MongoView
//
// Created by Shadowfacts on 4/4/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Foundation
import JavaScriptCore
struct ExtendedJSON {
private init() {}
private static let context: JSContext = {
let context = JSContext()!
let objectId: @convention(block) (String) -> [String: String] = { (id) in
return ["$oid": id]
}
context.setObject(objectId, forKeyedSubscript: "ObjectId" as NSString)
return context
}()
static func normalize(_ string: String) -> String? {
return context.evaluateScript("JSON.stringify(\(string))")?.toString()
}
}

View File

@ -19,23 +19,15 @@ fileprivate let identifierStarts: CharacterSet = {
return set return set
}() }()
fileprivate let operators = CharacterSet(charactersIn: "+-*/<>=") fileprivate let operators = CharacterSet(charactersIn: "+-*/<>=")
fileprivate let expressionEnds = CharacterSet(charactersIn: ",]});")
class JavaScriptHighlighter { class JavaScriptHighlighter {
private let text: String private let 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)]()
init(text: String) { init(text: String) {
self.text = text self.text = text
self.attributed = NSMutableAttributedString(attributedString: NSAttributedString(string: text))
}
init(mutableAttributed: NSMutableAttributedString) {
self.text = mutableAttributed.string
self.attributed = mutableAttributed
} }
private func print(_ str: String) { private func print(_ str: String) {
@ -69,7 +61,9 @@ class JavaScriptHighlighter {
return c return c
} }
func highlight() { func highlight(mutableAttributed: NSMutableAttributedString? = nil) -> NSAttributedString {
attributed = mutableAttributed ?? NSMutableAttributedString(attributedString: NSAttributedString(string: text))
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,
@ -77,9 +71,11 @@ class JavaScriptHighlighter {
], range: fullRange) ], range: fullRange)
currentIndex = text.startIndex currentIndex = text.startIndex
while let char = peek(), !expressionEnds.contains(char) { while currentIndex < text.endIndex {
consumeExpression() consumeExpression()
} }
return attributed
} }
private func emit(token: TokenType, range: NSRange) { private func emit(token: TokenType, range: NSRange) {
@ -95,7 +91,6 @@ class JavaScriptHighlighter {
return return
} }
attributed.addAttribute(.foregroundColor, value: color, range: range) attributed.addAttribute(.foregroundColor, value: color, range: range)
tokens.append((token, range))
} }
private func consumeExpression() { private func consumeExpression() {
@ -121,8 +116,6 @@ class JavaScriptHighlighter {
consumeArray() consumeArray()
} else if char == "." { } else if char == "." {
consumeDotLookup() consumeDotLookup()
} else if char == "?" {
consumeTernaryExpression()
} else { } else {
consume() consume()
} }
@ -175,42 +168,32 @@ class JavaScriptHighlighter {
private func consumeTemplateString() { private func consumeTemplateString() {
var stringFragmentStart: String.Index? = currentIndex var stringFragmentStart: String.Index? = currentIndex
func emitTemplateStringFragment() {
guard stringFragmentStart != currentIndex else { return }
print("Template string fragment: '\(text[stringFragmentStart!..<currentIndex])'")
emit(token: .string, range: range(from: stringFragmentStart!, to: currentIndex))
stringFragmentStart = currentIndex
}
consume() // opening ` consume() // opening `
while currentIndex < text.endIndex { while currentIndex < text.endIndex {
if peek() == "$" { if peek(length: 2) == "${" {
emitTemplateStringFragment()
consume() // $ consume() // $
if peek() == "{" { consume() // {
consume() // { print("Template string fragment: '\(text[stringFragmentStart!..<currentIndex])'")
emitTemplateStringFragment() emit(token: .string, range: range(from: stringFragmentStart!, to: currentIndex))
consumeTemplateStringExpression() consumeTemplateStringExpression()
stringFragmentStart = currentIndex stringFragmentStart = currentIndex
if currentIndex < text.endIndex && peek() == "}" { if currentIndex < text.endIndex && peek() == "}" {
consume() // } consume()
}
} }
} else if peek() == "`" { } else if peek() == "`" {
stringFragmentStart = stringFragmentStart ?? currentIndex stringFragmentStart = stringFragmentStart ?? currentIndex
consume() // ` consume() // `
emitTemplateStringFragment() print("Template string fragment: '\(text[stringFragmentStart!..<currentIndex])'")
emit(token: .string, range: range(from: stringFragmentStart!, to: currentIndex))
stringFragmentStart = nil stringFragmentStart = nil
break break
} else { } else {
consume() consume()
} }
} }
if stringFragmentStart != nil { if let start = stringFragmentStart {
emitTemplateStringFragment() print("Template string fragment: '\(text[start..<currentIndex])'")
emit(token: .string, range: range(from: start, to: currentIndex))
} }
} }
@ -303,7 +286,7 @@ class JavaScriptHighlighter {
indent += " " indent += " "
array: array:
while currentIndex < text.endIndex && peek() != "]" { while currentIndex < text.endIndex && peek() != "]" {
consumeWhitespace() print("Array element")
while currentIndex < text.endIndex { while currentIndex < text.endIndex {
if peek() == "," { if peek() == "," {
consume() // , consume() // ,
@ -314,7 +297,6 @@ class JavaScriptHighlighter {
break array break array
} else { } else {
indent += " " indent += " "
print("Array element")
consumeExpression() consumeExpression()
indent = String(indent.dropLast(2)) indent = String(indent.dropLast(2))
} }
@ -328,22 +310,6 @@ class JavaScriptHighlighter {
} }
} }
func consumeTernaryExpression() {
consume() // ?
print("Ternary expression")
indent += " "
print("Ternary true result")
while let char = peek(), char != ":" {
consumeExpression() // true result
}
consume() // :
print("Ternary false result")
while let char = peek(), !expressionEnds.contains(char) {
consumeExpression()
}
indent = String(indent.dropLast(2))
}
} }
extension JavaScriptHighlighter { extension JavaScriptHighlighter {

View File

@ -140,7 +140,7 @@ class DatabaseViewController: NSViewController {
} }
} else if let collection = item as? DatabaseCollection { } else if let collection = item as? DatabaseCollection {
// only open a new window/tab if our own query has changed from the default, otherwise replace our query controller // only open a new window/tab if our own query has changed from the default, otherwise replace our query controller
if let queryViewController = queryViewController, queryViewController.hasFilterChanged { if let queryViewController = queryViewController, queryViewController.hasQueryChanged {
(NSApplication.shared.delegate as! AppDelegate).newWindow(mongoController: mongoController, collection: collection) (NSApplication.shared.delegate as! AppDelegate).newWindow(mongoController: mongoController, collection: collection)
} else { } else {
self.selectedCollection = collection self.selectedCollection = collection
@ -149,6 +149,10 @@ class DatabaseViewController: NSViewController {
} }
} }
@IBAction func runQuery(_ sender: Any) {
queryViewController?.runQuery()
}
@IBAction func refresh(_ sender: Any) { @IBAction func refresh(_ sender: Any) {
queryViewController?.refresh() queryViewController?.refresh()
} }
@ -156,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(refresh(_:)) { if menuItem.action == #selector(runQuery(_:)) || menuItem.action == #selector(refresh(_:)) {
return queryViewController != nil return queryViewController != nil
} }
return true return true

View File

@ -12,19 +12,19 @@ import MongoSwift
class QueryViewController: NSViewController { class QueryViewController: NSViewController {
@IBOutlet weak var verticalSplitView: NSSplitView! @IBOutlet weak var verticalSplitView: NSSplitView!
@IBOutlet var filterTextView: JavaScriptEditorView! @IBOutlet var queryTextView: NSTextView!
@IBOutlet weak var outlineView: NSOutlineView! @IBOutlet weak var outlineView: NSOutlineView!
@IBOutlet weak var documentCountLabel: NSTextField! @IBOutlet weak var documentCountLabel: NSTextField!
let mongoController: MongoController let mongoController: MongoController
let collection: DatabaseCollection let collection: DatabaseCollection
var defaultFilter: String { var defaultQuery: String {
"{}" "db.getCollection('\(collection.name)').find({}).toArray()"
} }
var hasFilterChanged: Bool { var hasQueryChanged: Bool {
return filterTextView.string != defaultFilter return queryTextView.string != defaultQuery
} }
var mostRecentQuery: String? = nil var mostRecentQuery: String? = nil
@ -50,10 +50,12 @@ 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) queryTextView.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
filterTextView.isAutomaticQuoteSubstitutionEnabled = false queryTextView.isAutomaticQuoteSubstitutionEnabled = false
filterTextView.string = defaultFilter queryTextView.string = defaultQuery
queryTextView.delegate = self
highlightQuery()
outlineView.dataSource = self outlineView.dataSource = self
outlineView.delegate = self outlineView.delegate = self
@ -75,29 +77,38 @@ 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) {
let filterText = filterTextView.string.trimmingCharacters(in: .whitespacesAndNewlines) if let query = mostRecentQuery {
let filter: Document let connStr = "\(mongoController.connectionString)/\(collection.database)"
if !filterText.isEmpty,
let normalized = ExtendedJSON.normalize(filterText), rootNodes = MongoEvaluator.eval(command: query, connectingTo: connStr).map {
let doc = try? Document(fromJSON: normalized) { Node(value: $0)
filter = doc }
title = query
documentCountLabel.stringValue = "\(rootNodes.count) result\(rootNodes.count == 1 ? "" : "s")"
} else { } else {
filter = [:] let documents = try! mongoController.collection(collection).find().all()
} rootNodes = documents.map { Node(document: $0) }
let documents = try! mongoController.collection(collection).find(filter).all()
rootNodes = documents.map { Node(document: $0) }
title = "\(self.collection.database).\(self.collection.name)" title = "\(self.collection.database).\(self.collection.name)"
documentCountLabel.stringValue = "\(documents.count) document\(documents.count == 1 ? "" : "s")" documentCountLabel.stringValue = "\(documents.count) document\(documents.count == 1 ? "" : "s")"
}
if reload { if reload {
outlineView.reloadData() outlineView.reloadData()
} }
} }
func runQuery() {
mostRecentQuery = queryTextView.string
refresh()
}
func deleteRootNode(_ node: Node) { func deleteRootNode(_ node: Node) {
guard case let .document(doc) = node.value else { return } guard case let .document(doc) = node.value else { return }
let alert = NSAlert() let alert = NSAlert()
@ -165,14 +176,16 @@ 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(_:)) {
if outlineView.clickedRow != -1, let node = outlineView.item(atRow: outlineView.clickedRow) as? Node, node.parent == nil { return outlineView.clickedRow != -1
return true
} else {
return false
}
} }
return true return true
} }

View File

@ -1,15 +1,15 @@
<?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="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16085" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16085"/>
<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="QueryViewController" customModule="MongoView" customModuleProvider="target"> <customObject id="-2" userLabel="File's Owner" customClass="QueryViewController" customModule="MongoView" customModuleProvider="target">
<connections> <connections>
<outlet property="documentCountLabel" destination="bwd-po-tUv" id="5Ip-AZ-nVc"/> <outlet property="documentCountLabel" destination="bwd-po-tUv" id="5Ip-AZ-nVc"/>
<outlet property="filterTextView" destination="f8D-lV-IMK" id="dq6-lR-LeP"/>
<outlet property="outlineView" destination="uu9-9q-MWr" id="BEH-jX-h0S"/> <outlet property="outlineView" destination="uu9-9q-MWr" id="BEH-jX-h0S"/>
<outlet property="queryTextView" destination="f8D-lV-IMK" id="dq6-lR-LeP"/>
<outlet property="verticalSplitView" destination="E3D-CM-hk3" id="3Ks-1t-OFW"/> <outlet property="verticalSplitView" destination="E3D-CM-hk3" id="3Ks-1t-OFW"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/> <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections> </connections>
@ -38,7 +38,7 @@
<rect key="frame" x="0.0" y="0.0" width="726" height="86"/> <rect key="frame" x="0.0" y="0.0" width="726" height="86"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" textCompletion="NO" id="f8D-lV-IMK" customClass="JavaScriptEditorView" customModule="MongoView" customModuleProvider="target"> <textView importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" textCompletion="NO" id="f8D-lV-IMK">
<rect key="frame" x="0.0" y="0.0" width="726" height="86"/> <rect key="frame" x="0.0" y="0.0" width="726" height="86"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>

View File

@ -1,85 +0,0 @@
//
// JavaScriptEditorView.swift
// MongoView
//
// Created by Shadowfacts on 4/4/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import AppKit
class JavaScriptEditorView: NSTextView {
var highlighter: JavaScriptHighlighter?
override var string: String {
didSet {
rehighlight()
}
}
func rehighlight() {
highlighter = JavaScriptHighlighter(mutableAttributed: self.textStorage!)
highlighter!.highlight()
}
override func shouldChangeText(in affectedCharRange: NSRange, replacementString: String?) -> Bool {
guard super.shouldChangeText(in: affectedCharRange, replacementString: replacementString) else {
return false
}
if affectedCharRange.length == 0,
let string = replacementString,
string.count == 1,
tryAutocompleteCharacter(for: affectedCharRange, string: string) {
return false
}
return true
}
override func didChangeText() {
rehighlight()
super.didChangeText()
}
func tryAutocompleteCharacter(for range: NSRange, string inserted: String) -> Bool {
if let end = autocompleteResultFor(string: inserted, in: range) {
textStorage!.insert(NSAttributedString(string: "\(inserted)\(end)"), at: range.location)
didChangeText()
setSelectedRange(NSRange(location: range.location + 1, length: 0))
return true
} else {
return false
}
}
private func autocompleteResultFor(string: String, in range: NSRange) -> String? {
switch string {
case "'", "\"", "`":
return token(at: range.location) != .string ? string : nil
case "(":
return token(at: range.location) != .string ? ")" : nil
case "[":
return token(at: range.location) != .string ? "]" : nil
case "{":
var prevChar: Unicode.Scalar?
if range.location > 0 {
let index = self.string.index(self.string.startIndex, offsetBy: range.location - 1)
prevChar = self.string.unicodeScalars[index]
}
return token(at: range.location) != .string || (prevChar == "$") ? "}" : nil
default:
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
}
}

View File

@ -8,6 +8,6 @@
import Foundation import Foundation
let source = "`$foo ${blah} bar`" let source = "{a: 1234 , 'b': 'blah', foo}"
_ = JavaScriptHighlighter(text: source).highlight() _ = JavaScriptHighlighter(text: source).highlight()