2020-04-04 15:03:52 +00:00
|
|
|
//
|
|
|
|
// JavaScriptEditorView.swift
|
|
|
|
// MongoView
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 4/4/20.
|
|
|
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import AppKit
|
2020-04-10 01:58:53 +00:00
|
|
|
import Combine
|
2020-04-04 15:03:52 +00:00
|
|
|
|
|
|
|
class JavaScriptEditorView: NSTextView {
|
|
|
|
|
2020-04-09 03:16:46 +00:00
|
|
|
var highlighter = JavaScriptHighlighter()
|
|
|
|
private var isRehighlighting = false
|
2020-04-10 01:58:53 +00:00
|
|
|
|
2020-04-04 15:03:52 +00:00
|
|
|
override var string: String {
|
2020-04-09 03:16:46 +00:00
|
|
|
get {
|
|
|
|
super.string
|
|
|
|
}
|
|
|
|
set {
|
|
|
|
super.string = newValue
|
2020-04-04 15:03:52 +00:00
|
|
|
rehighlight()
|
|
|
|
}
|
|
|
|
}
|
2020-04-10 01:58:53 +00:00
|
|
|
|
|
|
|
private let rehighlightSubject = PassthroughSubject<Void, Never>()
|
|
|
|
private var debouncedRehighlightCancellable: AnyCancellable!
|
2020-04-04 15:03:52 +00:00
|
|
|
|
|
|
|
func rehighlight() {
|
2020-04-09 03:16:46 +00:00
|
|
|
isRehighlighting = true
|
|
|
|
highlighter.highlight(attributed: self.textStorage!)
|
|
|
|
isRehighlighting = false
|
|
|
|
}
|
|
|
|
|
|
|
|
override func awakeFromNib() {
|
|
|
|
super.awakeFromNib()
|
|
|
|
|
|
|
|
textStorage!.delegate = self
|
2020-04-10 01:58:53 +00:00
|
|
|
|
|
|
|
typingAttributes = [
|
|
|
|
.font: NSFont.monospacedSystemFont(ofSize: 13, weight: .regular),
|
|
|
|
.foregroundColor: NSColor.textColor
|
|
|
|
]
|
|
|
|
|
|
|
|
debouncedRehighlightCancellable = rehighlightSubject.throttle(for: .milliseconds(40), scheduler: RunLoop.main, latest: false).sink(receiveValue: rehighlight)
|
2020-04-04 15:03:52 +00:00
|
|
|
}
|
|
|
|
|
2020-04-04 17:07:54 +00:00
|
|
|
override func shouldChangeText(in affectedCharRange: NSRange, replacementString: String?) -> Bool {
|
|
|
|
guard super.shouldChangeText(in: affectedCharRange, replacementString: replacementString) else {
|
|
|
|
return false
|
|
|
|
}
|
2020-04-09 03:16:46 +00:00
|
|
|
|
2020-04-04 17:07:54 +00:00
|
|
|
if affectedCharRange.length == 0,
|
|
|
|
let string = replacementString,
|
|
|
|
string.count == 1,
|
|
|
|
tryAutocompleteCharacter(for: affectedCharRange, string: string) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2020-04-09 03:16:46 +00:00
|
|
|
rehighlight()
|
|
|
|
super.didChangeText()
|
2020-04-04 17:07:54 +00:00
|
|
|
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 "'", "\"", "`":
|
2020-04-09 03:16:46 +00:00
|
|
|
if case .string(_) = highlighter.token(at: range.location) {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return string
|
|
|
|
}
|
2020-04-04 17:07:54 +00:00
|
|
|
case "(":
|
2020-04-09 03:16:46 +00:00
|
|
|
if case .string(_) = highlighter.token(at: range.location) {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return ")"
|
|
|
|
}
|
2020-04-04 17:07:54 +00:00
|
|
|
case "[":
|
2020-04-09 03:16:46 +00:00
|
|
|
if case .string(_) = highlighter.token(at: range.location) {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return "]"
|
|
|
|
}
|
2020-04-04 17:07:54 +00:00
|
|
|
case "{":
|
|
|
|
if range.location > 0 {
|
|
|
|
let index = self.string.index(self.string.startIndex, offsetBy: range.location - 1)
|
2020-04-09 03:16:46 +00:00
|
|
|
let prevChar = self.string.unicodeScalars[index]
|
|
|
|
if prevChar == "$" {
|
|
|
|
return "}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if case .string(_) = highlighter.token(at: range.location) {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return "}"
|
2020-04-04 17:07:54 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-09 03:16:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2020-04-04 17:07:54 +00:00
|
|
|
}
|
2020-04-09 03:16:46 +00:00
|
|
|
|
2020-04-10 01:58:53 +00:00
|
|
|
rehighlightSubject.send()
|
2020-04-04 17:07:54 +00:00
|
|
|
}
|
2020-04-04 15:03:52 +00:00
|
|
|
}
|