Skip to content

Commit

Permalink
Improve syntax highlighting speed, move some views to code
Browse files Browse the repository at this point in the history
  • Loading branch information
lukakerr committed Feb 17, 2019
1 parent a6865eb commit cfbfaab
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 137 deletions.
44 changes: 0 additions & 44 deletions Twig/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -944,51 +944,7 @@
<view key="view" wantsLayer="YES" id="ERx-hH-rdd" customClass="MarkdownView" customModule="Twig" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="500" height="600"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView focusRingType="none" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LAD-f9-Lpr">
<rect key="frame" x="0.0" y="0.0" width="500" height="600"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="s9D-Lv-cP8">
<rect key="frame" x="0.0" y="0.0" width="500" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView focusRingType="none" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="bar" allowsUndo="YES" usesRuler="YES" smartInsertDelete="YES" id="SNH-fR-eeK">
<rect key="frame" x="0.0" y="0.0" width="500" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="500" height="600"/>
<size key="maxSize" width="800" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
<connections>
<binding destination="5gI-5U-AMq" name="attributedString" keyPath="attributedMarkdownTextInput" id="NRg-aq-nt4">
<dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary>
</binding>
</connections>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="pla-W4-kOj">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="2Fr-Xo-YM2">
<rect key="frame" x="484" y="0.0" width="16" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="LAD-f9-Lpr" firstAttribute="top" secondItem="ERx-hH-rdd" secondAttribute="top" id="1hl-zR-pdl"/>
<constraint firstAttribute="bottom" secondItem="LAD-f9-Lpr" secondAttribute="bottom" id="91h-re-9b3"/>
<constraint firstAttribute="trailing" secondItem="LAD-f9-Lpr" secondAttribute="trailing" id="B2C-hK-WSB"/>
<constraint firstItem="LAD-f9-Lpr" firstAttribute="leading" secondItem="ERx-hH-rdd" secondAttribute="leading" id="QK6-F3-40T"/>
</constraints>
</view>
<connections>
<outlet property="markdownTextView" destination="SNH-fR-eeK" id="W8s-xh-NHu"/>
</connections>
</viewController>
<customObject id="2Tp-Fl-jBw" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<userDefaultsController representsSharedInstance="YES" id="SCX-Ho-naq"/>
Expand Down
227 changes: 140 additions & 87 deletions Twig/Controllers/MarkdownViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@
//

import Cocoa
import Highlightr
import cmark_gfm_swift

class MarkdownViewController: NSViewController, NSTextViewDelegate {
let MARKDOWN_SYNTAX = "Markdown"

@IBOutlet var markdownTextView: NSTextView!
class MarkdownViewController: NSViewController, NSTextViewDelegate, HighlightDelegate {

private var scrollView: NSScrollView!
private var markdownTextView: NSTextView!
private var layoutManager: NSLayoutManager!

public var textStorage: CodeAttributedString!

private var debouncedGeneratePreview: Debouncer!

Expand All @@ -25,18 +32,6 @@ class MarkdownViewController: NSViewController, NSTextViewDelegate {
return splitViewController?.splitViewItems.last?.viewController as? PreviewViewController
}

/// Cocoa binding for text inside markdownTextView
@objc var attributedMarkdownTextInput: NSAttributedString {
get {
return NSAttributedString(string: markdownTextView.string)
}
set {
syntaxHighlight()
debouncedGeneratePreview.call()
setWordCount()
}
}

override var acceptsFirstResponder: Bool {
return true
}
Expand All @@ -58,16 +53,141 @@ class MarkdownViewController: NSViewController, NSTextViewDelegate {
preview.isCollapsed = !preferences.showPreviewOnStartup
}

self.setupTextStorage()
self.setupScrollView()
self.setupLayoutManager()
self.setupMarkdownTextView()

if let textContainer = markdownTextView.textContainer {
layoutManager.addTextContainer(textContainer)
}

scrollView.documentView = markdownTextView

view.addSubview(scrollView)
}

// MARK: - Private functions for updating and setting view components

func setupTextStorage() {
textStorage = CodeAttributedString(highlightr: theme.highlightr)
textStorage.highlightDelegate = self
textStorage.language = MARKDOWN_SYNTAX
}

func setupScrollView() {
scrollView = NSScrollView()
scrollView.frame = view.frame
scrollView.borderType = .noBorder
scrollView.drawsBackground = false
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = false
scrollView.autoresizingMask = [.width, .height]
}

func setupLayoutManager() {
layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
}

func setupMarkdownTextView() {
markdownTextView = NSTextView()
markdownTextView.delegate = self
markdownTextView.allowsUndo = true
markdownTextView.isEditable = true
markdownTextView.usesFindBar = true
markdownTextView.isRichText = false
markdownTextView.isSelectable = true
markdownTextView.frame = view.bounds
markdownTextView.font = preferences.font
markdownTextView.drawsBackground = false
markdownTextView.frame = scrollView.bounds
markdownTextView.autoresizingMask = [.width]
markdownTextView.insertionPointColor = .gray
markdownTextView.isVerticallyResizable = true
markdownTextView.isHorizontallyResizable = false
markdownTextView.textContainerInset = NSSize(width: 10.0, height: 10.0)
}

override func viewDidAppear() {
reloadUI()
/// Update any UI related components
@objc private func reloadUI() {
syntaxHighlight()
view.updateLayer()
generatePreview()
markdownTextView.isContinuousSpellCheckingEnabled = preferences.spellcheckEnabled
}

/// Sets the word count in the titlebar word count accessory
private func setWordCount() {
let wordCountView = view.window?.titlebarAccessoryViewControllers.first?.view.subviews.first as? NSTextField
guard let wordCount = markdownTextView.textStorage?.words.count else { return }

var countString = String(describing: wordCount) + " word"

if wordCount > 1 {
countString += "s"
} else if wordCount < 1 {
countString = ""
}
wordCountView?.stringValue = countString
}

// MARK: - Functions handling markdown editing

/// Called when the syntax highlighter finishes
func didHighlight(_ range: NSRange, success: Bool) {
setWordCount()
debouncedGeneratePreview.call()
}

/// Syntax highlight the entire markdownTextView contents
private func syntaxHighlight() {
let markdownText = markdownTextView.string

DispatchQueue.global(qos: .userInitiated).async {
theme.setFont(to: preferences.font)
// self.textStorage.highlightr.theme.setCodeFont(preferences.font)

guard
let highlightedCode = self.textStorage.highlightr.highlight(markdownText, as: MARKDOWN_SYNTAX)
else { return }

DispatchQueue.main.async {
let cursorPosition = self.markdownTextView.selectedRanges[0].rangeValue.location
self.textStorage.beginEditing()
self.textStorage.setAttributedString(highlightedCode)
self.textStorage.endEditing()
self.markdownTextView.setSelectedRange(NSRange(location: cursorPosition, length: 0))
}
}
}

/// Parse the markdownTextView contents into HTML and load it into the webview
@objc private func generatePreview() {
// If preview is collapsed, return
guard
let preview = splitViewController?.splitViewItems.last,
!preview.isCollapsed
else { return }

// Don't escape a double backslash
let markdownText = markdownTextView.string.replacingOccurrences(of: "\\", with: "\\\\")

DispatchQueue.global(qos: .userInitiated).async {
if let parsed = Node(markdown: markdownText)?.html {
DispatchQueue.main.async {
self.previewViewController?.captureScroll {
self.previewViewController?.webPreview.loadHTMLString(html.getHTML(with: parsed), baseURL: nil)
}
}
}
}
}

}

extension MarkdownViewController {

// MARK: - First responder methods for exporting from WKWebView

@IBAction func exportPDF(sender: NSMenuItem) {
Expand All @@ -90,6 +210,10 @@ class MarkdownViewController: NSViewController, NSTextViewDelegate {
XMLExporter.export(from: markdownTextView)
}

}

extension MarkdownViewController {

// MARK: - First responder methods for various markdown formatting shortcuts

@IBAction func bold(sender: NSMenuItem) {
Expand Down Expand Up @@ -157,75 +281,4 @@ class MarkdownViewController: NSViewController, NSTextViewDelegate {
reloadUI()
}

// MARK: - Functions handling markdown editing

/// Syntax highlight the markdownTextView contents
private func syntaxHighlight() {
let markdownText = markdownTextView.string

DispatchQueue.global(qos: .userInitiated).async {
let highlightedCode = theme.highlightr.highlight(markdownText, as: "markdown")

if let syntaxHighlighted = highlightedCode {
let code = NSMutableAttributedString(attributedString: syntaxHighlighted)
code.withFont(preferences.font)

DispatchQueue.main.async {
let cursorPosition = self.markdownTextView.selectedRanges[0].rangeValue.location
self.markdownTextView.textStorage?.beginEditing()
self.markdownTextView.textStorage?.setAttributedString(code)
self.markdownTextView.textStorage?.endEditing()
self.markdownTextView.setSelectedRange(NSRange(location: cursorPosition, length: 0))
}
}
}
}

/// Parse the markdownTextView contents into HTML and load it into the webview
@objc private func generatePreview() {
// If preview is collapsed, return
guard
let preview = splitViewController?.splitViewItems.last,
!preview.isCollapsed
else { return }

// Don't escape a double backslash
let markdownText = markdownTextView.string.replacingOccurrences(of: "\\", with: "\\\\")

DispatchQueue.global(qos: .userInitiated).async {
if let parsed = Node(markdown: markdownText)?.html {
DispatchQueue.main.async {
self.previewViewController?.captureScroll {
self.previewViewController?.webPreview.loadHTMLString(html.getHTML(with: parsed), baseURL: nil)
}
}
}
}
}

// MARK: - Private functions for updating and setting view components

/// Update any UI related components
@objc private func reloadUI() {
syntaxHighlight()
view.updateLayer()
self.generatePreview()
self.markdownTextView.isContinuousSpellCheckingEnabled = preferences.spellcheckEnabled
}

/// Sets the word count in the titlebar word count accessory
private func setWordCount() {
let wordCountView = view.window?.titlebarAccessoryViewControllers.first?.view.subviews.first as? NSTextField
guard let wordCount = markdownTextView.textStorage?.words.count else { return }

var countString = String(describing: wordCount) + " word"

if wordCount > 1 {
countString += "s"
} else if wordCount < 1 {
countString = ""
}
wordCountView?.stringValue = countString
}

}
7 changes: 5 additions & 2 deletions Twig/Controllers/SidebarViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class SidebarViewController: NSViewController {

sidebar.backgroundColor = backgroundColor
sidebarActionsView.setBackgroundColor(backgroundColor)
setRowColour(sidebar)
sidebar.reloadData()
}

Expand Down Expand Up @@ -252,8 +253,10 @@ extension SidebarViewController: NSOutlineViewDataSource {

private func setRowTextColor(forRow row: NSTableRowView) {
if let cell = row.view(atColumn: 0) as? NSTableCellView {
let textColor: NSColor = row.isSelected || sidebar.backgroundColor.isDark
? .white : .black
let isDark = sidebar.backgroundColor.isDark
let selectedAndDark = row.isSelected && isDark

let textColor: NSColor = isDark || selectedAndDark ? .white : .black

cell.textField?.textColor = textColor
}
Expand Down
2 changes: 1 addition & 1 deletion Twig/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>markdown</string>
<string>md</string>
<string>markdown</string>
</array>
<key>CFBundleTypeIconFile</key>
<string></string>
Expand Down
5 changes: 2 additions & 3 deletions Twig/Models/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Document: NSDocument {

// Returns data used to save the file
override func data(ofType typeName: String) throws -> Data {
guard let data = self.markdownVC?.markdownTextView.textStorage?.string.data(using: .utf8) else {
guard let data = self.markdownVC?.textStorage.string.data(using: .utf8) else {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
return data
Expand Down Expand Up @@ -110,8 +110,7 @@ class Document: NSDocument {

fileprivate func setContents() {
if let data = self.fileData, let contents = String(data: data, encoding: .utf8) {
self.markdownVC?.markdownTextView.string = contents
self.markdownVC?.attributedMarkdownTextInput = NSAttributedString(string: contents)
self.markdownVC?.textStorage.setAttributedString(NSAttributedString(string: contents))
}
}

Expand Down
3 changes: 3 additions & 0 deletions Twig/Models/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class Preferences {
if defaults.object(forKey: PreferencesKeys.useThemeColorForSidebar) != nil {
useThemeColorForSidebar = defaults.bool(forKey: PreferencesKeys.useThemeColorForSidebar)
}

theme.setFont(to: self.font)
}

public var showPreviewOnStartup = true {
Expand Down Expand Up @@ -136,6 +138,7 @@ class Preferences {
}

set {
theme.setFont(to: newValue)
setDefaults(key: PreferencesKeys.fontName, newValue.fontName)
setDefaults(key: PreferencesKeys.fontSize, newValue.pointSize)
}
Expand Down

0 comments on commit cfbfaab

Please sign in to comment.