From 2088a910988a9118a97ef0c1a0adeb2f92ace463 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 4 Apr 2020 13:07:54 -0400 Subject: [PATCH] JavaScript editor: add autocompletion for ', ", `, (, [, and { --- .../JavaScriptHighlighter.swift | 38 ++++++++---- MongoView/Views/JavaScriptEditorView.swift | 59 ++++++++++++++++++- jstest/main.swift | 2 +- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/MongoView/Synax Highlighting/JavaScriptHighlighter.swift b/MongoView/Synax Highlighting/JavaScriptHighlighter.swift index 41657b8..e3e91a3 100644 --- a/MongoView/Synax Highlighting/JavaScriptHighlighter.swift +++ b/MongoView/Synax Highlighting/JavaScriptHighlighter.swift @@ -26,6 +26,7 @@ class JavaScriptHighlighter { private var attributed: NSMutableAttributedString private var currentIndex: String.Index! private var indent = "" + private(set) var tokens = [(token: TokenType, range: NSRange)]() init(text: String) { self.text = text @@ -94,6 +95,7 @@ class JavaScriptHighlighter { return } attributed.addAttribute(.foregroundColor, value: color, range: range) + tokens.append((token, range)) } private func consumeExpression() { @@ -173,32 +175,42 @@ class JavaScriptHighlighter { private func consumeTemplateString() { var stringFragmentStart: String.Index? = currentIndex + + func emitTemplateStringFragment() { + guard stringFragmentStart != currentIndex else { return } + print("Template string fragment: '\(text[stringFragmentStart!.. 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 + } } diff --git a/jstest/main.swift b/jstest/main.swift index 05cc648..ce627cf 100644 --- a/jstest/main.swift +++ b/jstest/main.swift @@ -8,6 +8,6 @@ import Foundation -let source = "a ? 'foo' : b ? 'bar' : 'baz'" +let source = "`$foo ${blah} bar`" _ = JavaScriptHighlighter(text: source).highlight()