MongoView/MongoView/Views/JavaScriptEditorView.swift

123 lines
3.7 KiB
Swift

//
// JavaScriptEditorView.swift
// MongoView
//
// Created by Shadowfacts on 4/4/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import AppKit
class JavaScriptEditorView: NSTextView {
var highlighter = JavaScriptHighlighter()
private var isRehighlighting = false
override var string: String {
get {
super.string
}
set {
isRehighlighting = true
super.string = newValue
rehighlight()
}
}
func rehighlight() {
isRehighlighting = true
highlighter.highlight(attributed: self.textStorage!)
isRehighlighting = false
}
override func awakeFromNib() {
super.awakeFromNib()
textStorage!.delegate = self
}
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
}
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)
rehighlight()
super.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 "'", "\"", "`":
if case .string(_) = highlighter.token(at: range.location) {
return nil
} else {
return string
}
case "(":
if case .string(_) = highlighter.token(at: range.location) {
return nil
} else {
return ")"
}
case "[":
if case .string(_) = highlighter.token(at: range.location) {
return nil
} else {
return "]"
}
case "{":
if range.location > 0 {
let index = self.string.index(self.string.startIndex, offsetBy: range.location - 1)
let prevChar = self.string.unicodeScalars[index]
if prevChar == "$" {
return "}"
}
}
if case .string(_) = highlighter.token(at: range.location) {
return nil
} else {
return "}"
}
default:
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()
}
}