Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Back references and theme with font, in pure Swift and SPM support #6

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
The MIT License (MIT)

Copyright (c) 2014-2015 Sam Soffes http://soff.es
Copyright (c) 2014-2016 Sam Soffes http://soff.es
Copyright (c) 2016-2021 Alexander Hedges.
Copyright (c) 2021 Zheng Wu.
All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
32 changes: 32 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "SyntaxKit",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "SyntaxKit",
targets: ["SyntaxKit"]
),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "SyntaxKit",
dependencies: []
),
.testTarget(
name: "SyntaxKitTests",
dependencies: ["SyntaxKit"],
exclude: ["Fixtures"]
),
]
)
File renamed without changes.
2 changes: 1 addition & 1 deletion SyntaxKit/Resources/Info.plist → Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015-2016 Sam Soffes. Copyright © 2016 Alexander Hedges. All rights reserved.</string>
<string>Copyright © 2015-2016 Sam Soffes. Copyright © 2016-2021 Alexander Hedges. Copyright © 2021 Zheng Wu. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
// Copyright © 2016 Alexander Hedges. All rights reserved.
//

import Foundation

/// Represents one change (insertion or deletion) between two strings
internal struct Diff {

Expand All @@ -26,6 +28,7 @@ internal struct Diff {
/// - Insertion: The inserted sting
/// - Deletion: The empty string
var change: String

/// The range of the change in the old string
///
/// - Insertion: The location of the insertion and length 0
Expand Down Expand Up @@ -60,23 +63,28 @@ open class AttributedParsingOperation: Operation {
///
/// The sender is passed in so it can be used to check if the operation was
/// cancelled after the call.
public typealias OperationCallback = ([(range: NSRange, attributes: Attributes?)], AttributedParsingOperation) -> Void
#if DEBUG
public typealias OperationTuple = (scope: String, range: NSRange, attributes: Attributes?)
#else
public typealias OperationTuple = (range: NSRange, attributes: Attributes?)
#endif
public typealias OperationCallback = ([OperationTuple], AttributedParsingOperation) -> Void

// MARK: - Properties

private let parser: AttributedParser
private let operationCallback: OperationCallback
private var parsedRange: NSRange?
private var outdatedRange: NSRange?

// MARK: - Initializers

/// Initializer for the first instance in the NSOperationQueue
///
/// Can also be used if no incremental parsing is desired
public init(string: String, language: Language, theme: Theme, callback: @escaping OperationCallback) {
parser = AttributedParser(language: language, theme: theme)
parser.toParse = ScopedString(string: string)
operationCallback = callback
self.parser = AttributedParser(language: language, theme: theme)
self.parser.toParse = ScopedString(string: string)
self.operationCallback = callback
super.init()
}

Expand All @@ -94,8 +102,8 @@ open class AttributedParsingOperation: Operation {
/// string that was added.
/// - parameter callback: The callback to call with results.
public init(string: String, previousOperation: AttributedParsingOperation, changeIsInsertion insertion: Bool, changedRange range: NSRange, newCallback callback: OperationCallback? = nil) {
parser = previousOperation.parser
operationCallback = callback ?? previousOperation.operationCallback
self.parser = AttributedParser(language: previousOperation.parser.language, theme: previousOperation.parser.theme)
self.operationCallback = callback ?? previousOperation.operationCallback

super.init()

Expand All @@ -106,8 +114,10 @@ open class AttributedParsingOperation: Operation {
diff = Diff(change: "", range: range)
}

if diff.representsChanges(from: parser.toParse.string, to: string) {
self.parsedRange = AttributedParsingOperation.outdatedRange(in: string as NSString, forChange: diff, updatingPreviousResult: &self.parser.toParse)
var prevParse = previousOperation.parser.toParse
if diff.representsChanges(from: prevParse.string, to: string) {
self.outdatedRange = AttributedParsingOperation.outdatedRange(in: string as NSString, forChange: diff, updatingPreviousResult: &prevParse)
self.parser.toParse = prevParse
} else {
self.parser.toParse = ScopedString(string: string)
}
Expand All @@ -116,22 +126,30 @@ open class AttributedParsingOperation: Operation {
// MARK: - NSOperation Implementation

open override func main() {
var resultsArray: [(range: NSRange, attributes: Attributes?)] = []
var resultsArray: [OperationTuple] = []
#if DEBUG
let callback = { (scope: String, range: NSRange, attributes: Attributes?) in
if let attributes = attributes {
resultsArray.append((scope, range, attributes))
}
}
#else
let callback = { (_: String, range: NSRange, attributes: Attributes?) in
if let attributes = attributes {
resultsArray.append((range, attributes))
}
}
#endif

parser.parse(in: self.parsedRange, match: callback)
self.parser.parse(in: self.outdatedRange, match: callback)

if !parser.aborted {
operationCallback(resultsArray, self)
if !self.parser.aborted {
self.operationCallback(resultsArray, self)
}
}

open override func cancel() {
parser.aborted = true
self.parser.aborted = true
super.cancel()
}

Expand All @@ -157,7 +175,7 @@ open class AttributedParsingOperation: Operation {
///
/// - returns: A range in newString that can be safely re-parsed. Or nil if
/// everything has to be reparsed.
class func outdatedRange(in newString: NSString, forChange diff: Diff, updatingPreviousResult previous: inout ScopedString) -> NSRange? {
class func outdatedRange(in newString: NSString, forChange diff: Diff, updatingPreviousResult previous: inout ScopedString) -> NSRange {
Copy link
Author

@Lessica Lessica Mar 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outdated range calculated here sometimes seems incorrent...
Especially near a new line separator. Any ideas?

let linesRange: NSRange
let range: NSRange
if diff.isInsertion() {
Expand Down
38 changes: 38 additions & 0 deletions Sources/SyntaxKit/Attributes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Attributes.swift
//
//
// Created by Zheng Wu on 2021/3/10.
// Copyright © 2021 Zheng Wu. All rights reserved.
//

import Foundation

public typealias Attributes = [NSAttributedString.Key: Any]

public extension NSAttributedString.Key {
// Shared
static let foreground = NSAttributedString.Key("foreground")
static let background = NSAttributedString.Key("background")

// Text Only
static let fontName = NSAttributedString.Key("fontName")
static let fontSize = NSAttributedString.Key("fontSize")
static let fontStyle = NSAttributedString.Key("fontStyle")
static let caret = NSAttributedString.Key("caret")
static let selection = NSAttributedString.Key("selection")
static let invisibles = NSAttributedString.Key("invisibles")
static let lineHighlight = NSAttributedString.Key("lineHighlight")

// Gutter Only
static let divider = NSAttributedString.Key("divider")
static let selectionBorder = NSAttributedString.Key("selectionBorder")
static let icons = NSAttributedString.Key("icons")
static let iconsHover = NSAttributedString.Key("iconsHover")
static let iconsPressed = NSAttributedString.Key("iconsPressed")
static let selectionForeground = NSAttributedString.Key("selectionForeground")
static let selectionBackground = NSAttributedString.Key("selectionBackground")
static let selectionIcons = NSAttributedString.Key("selectionIcons")
static let selectionIconsHover = NSAttributedString.Key("selectionIconsHover")
static let selectionIconsPressed = NSAttributedString.Key("selectionIconsPressed")
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// Copyright © 2016 Alexander Hedges. All rights reserved.
//

import Foundation

open class BundleManager {

public enum TextMateFileType {
Expand All @@ -37,10 +39,15 @@ open class BundleManager {
/// has to be done separately using clearLanguageCache.
open var languageCaching: Bool = true

/// You probably want to leave the themeCaching property set to true.
///
/// - note: Setting it to false will not invalidate or purge the cache. This
/// has to be done separately using clearThemeCache.
open var themeCaching: Bool = true

public static var defaultManager: BundleManager?

private var bundleCallback: BundleLocationCallback
private var dependencies: [Language] = []
private var cachedLanguages: [String: Language] = [:]
private var cachedThemes: [String: Theme] = [:]

Expand Down Expand Up @@ -70,56 +77,78 @@ open class BundleManager {
return language
}

self.dependencies = []
var language = self.loadRawLanguage(withIdentifier: identifier)
language?.validate(with: self.dependencies)
guard let newLanguage = includeLanguage(withIdentifier: identifier) else {
return nil
}

var languageSet = Set<Language>(arrayLiteral: newLanguage)
var languageDependencies = [Language]()

while let language = languageSet.popFirst() {
languageDependencies.append(language)
for childLanguageRef in language.referencedLanguageRefs {
if languageDependencies.map({ $0.scopeName }).contains(childLanguageRef) {
continue
}
guard let childLanguage = includeLanguage(withIdentifier: childLanguageRef) else {
continue
}
languageSet.insert(childLanguage)
}
}

// Now we finally got all helper languages
newLanguage.validate(with: languageDependencies)

if languageCaching && language != nil {
self.cachedLanguages[identifier] = language
if languageCaching {
self.cachedLanguages[identifier] = newLanguage
}

self.dependencies = []
return language
return newLanguage
}

open func theme(withIdentifier identifier: String) -> Theme? {
open func theme(withIdentifier identifier: String, fontCallback: Theme.FontCallback? = nil) -> Theme? {
if let theme = cachedThemes[identifier] {
return theme
}

guard let dictURL = self.bundleCallback(identifier, .theme),
let plist = NSDictionary(contentsOf: dictURL) as? [String: Any],
let newTheme = Theme(dictionary: plist) else {
return nil
guard let newTheme = includeTheme(withIdentifier: identifier, fontCallback: fontCallback) else {
return nil
}

cachedThemes[identifier] = newTheme
if themeCaching {
self.cachedThemes[identifier] = newTheme
}
return newTheme
}

/// Clears the language cache. Use if low on memory.
open func clearLanguageCache() {
self.cachedLanguages = [:]
/// Use if low on memory.
open func clearCaches() {
self.cachedLanguages.removeAll()
self.cachedThemes.removeAll()
}

// MARK: - Internal Interface

/// - parameter identifier: The identifier of the requested language.
/// - returns: The Language with unresolved extenal references, if found
func loadRawLanguage(withIdentifier identifier: String) -> Language? {
let indexOfStoredLanguage = self.dependencies.firstIndex { (lang: Language) in lang.scopeName == identifier }

if let index = indexOfStoredLanguage {
return self.dependencies[index]
} else {
guard let dictURL = self.bundleCallback(identifier, .language),
let plist = NSDictionary(contentsOf: dictURL) as? [String: Any],
let newLanguage = Language(dictionary: plist, manager: self) else {
return nil
}

self.dependencies.append(newLanguage)
return newLanguage
/// - returns: The Language with unresolved extenal references, if found.
private func includeLanguage(withIdentifier identifier: String) -> Language? {
guard let dictURL = self.bundleCallback(identifier, .language),
let plist = NSDictionary(contentsOf: dictURL) as? [String: Any],
let newLanguage = Language(dictionary: plist, manager: self) else {
return nil
}
return newLanguage
}

/// - parameter identifier: The identifier of the requested theme.
/// - returns: The Theme with unresolved extenal references, if found.
private func includeTheme(withIdentifier identifier: String, fontCallback: Theme.FontCallback? = nil) -> Theme? {
guard let dictURL = self.bundleCallback(identifier, .theme),
let plist = NSDictionary(contentsOf: dictURL) as? [String: Any],
let newTheme = Theme(dictionary: plist, fontCallback: fontCallback) else {
return nil
}
return newTheme
}
}
2 changes: 2 additions & 0 deletions SyntaxKit/Capture.swift → Sources/SyntaxKit/Capture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// Copyright © 2014-2015 Sam Soffes. All rights reserved.
//

import Foundation

internal struct Capture {

// MARK: - Properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// Copyright © 2014-2015 Sam Soffes. All rights reserved.
//

import Foundation

internal struct CaptureCollection {

// MARK: - Properties
Expand Down
2 changes: 1 addition & 1 deletion SyntaxKit/Color.swift → Sources/SyntaxKit/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2015 Sam Soffes. All rights reserved.
//

#if os(OSX)
#if os(macOS)
import AppKit.NSColor
public typealias ColorType = NSColor

Expand Down
Loading