-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e003916
Showing
10 changed files
with
592 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# Xcode | ||
# | ||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore | ||
|
||
## User settings | ||
xcuserdata/ | ||
|
||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) | ||
*.xcscmblueprint | ||
*.xccheckout | ||
|
||
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) | ||
build/ | ||
DerivedData/ | ||
*.moved-aside | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
|
||
## Obj-C/Swift specific | ||
*.hmap | ||
|
||
## App packaging | ||
*.ipa | ||
*.dSYM.zip | ||
*.dSYM | ||
|
||
## Playgrounds | ||
timeline.xctimeline | ||
playground.xcworkspace | ||
|
||
# Swift Package Manager | ||
# | ||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. | ||
# Packages/ | ||
# Package.pins | ||
Package.resolved | ||
# *.xcodeproj | ||
|
||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata | ||
# hence it is not needed unless you have added a package configuration file to your project | ||
.swiftpm | ||
|
||
.build/ | ||
|
||
# CocoaPods | ||
# | ||
# We recommend against adding the Pods directory to your .gitignore. However | ||
# you should judge for yourself, the pros and cons are mentioned at: | ||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control | ||
# | ||
Pods/ | ||
# | ||
# Add this line if you want to avoid checking in source code from the Xcode workspace | ||
*.xcworkspace | ||
|
||
# Carthage | ||
# | ||
# Add this line if you want to avoid checking in source code from Carthage dependencies. | ||
# Carthage/Checkouts | ||
|
||
Carthage/Build/ | ||
|
||
# Accio dependency management | ||
Dependencies/ | ||
.accio/ | ||
|
||
# fastlane | ||
# | ||
# It is recommended to not store the screenshots in the git repo. | ||
# Instead, use fastlane to re-generate the screenshots whenever they are needed. | ||
# For more information about the recommended setup visit: | ||
# https://docs.fastlane.tools/best-practices/source-control/#source-control | ||
|
||
fastlane/report.xml | ||
fastlane/Preview.html | ||
fastlane/screenshots/**/*.png | ||
fastlane/test_output | ||
|
||
# Code Injection | ||
# | ||
# After new code Injection tools there's a generated folder /iOSInjectionProject | ||
# https://github.com/johnno1962/injectionforxcode | ||
|
||
iOSInjectionProject/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Jiaan Fang | ||
|
||
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: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// swift-tools-version: 5.8 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "NeonCodeEditLanguagesPlugin", | ||
platforms: [.macOS(.v13)], | ||
products: [ | ||
.library( | ||
name: "NeonCodeEditLanguagesPlugin", | ||
targets: ["NeonCodeEditLanguagesPlugin"]), | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/krzyzanowskim/STTextView", from: "0.8.13"), | ||
.package(url: "https://github.com/ChimeHQ/Neon.git", from: "0.6.0"), | ||
.package(url: "https://github.com/CodeEditApp/CodeEditLanguages", from: "0.1.18") | ||
], | ||
targets: [ | ||
.target( | ||
name: "NeonCodeEditLanguagesPlugin", | ||
dependencies: [ | ||
.product(name: "STTextView", package: "STTextView"), | ||
"Neon", | ||
.product(name: "CodeEditLanguages", package: "CodeEditLanguages") | ||
] | ||
) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
[STTextView](https://github.com/krzyzanowskim/STTextView) Source Code Syntax Highlighting with [TreeSitter](https://tree-sitter.github.io/tree-sitter/), [Neon](https://github.com/ChimeHQ/Neon) and [CodeEditLanguages](https://github.com/CodeEditApp/CodeEditLanguages). | ||
|
||
Most code in this repo is borrowed from [STTextView-Plugin-Neon](https://github.com/krzyzanowskim/STTextView-Plugin-Neon), with some adaptations to work with CodeEditLanguages. | ||
|
||
|
||
## Installation | ||
|
||
Add the plugin package as a dependency of your application, then register/add it to the STTextView instance: | ||
|
||
```swift | ||
import NeonCodeEditLanguagesPlugin | ||
|
||
textView.addPlugin( | ||
NeonCodeEditLanguagesPlugin( | ||
theme: .default, | ||
language: .go | ||
) | ||
) | ||
``` | ||
|
||
SwiftUI: | ||
```swift | ||
import SwiftUI | ||
import STTextViewUI | ||
import NeonCodeEditLanguagesPlugin | ||
|
||
struct ContentView: View { | ||
@State private var text: AttributedString = "" | ||
@State private var selection: NSRange? | ||
var body: some View { | ||
STTextViewUI.TextView( | ||
text: $text, | ||
selection: $selection, | ||
options: [.wrapLines, .highlightSelectedLine], | ||
plugins: [NeonCodeEditLanguagesPlugin(theme: .default, language: .go)] | ||
) | ||
.textViewFont(.monospacedDigitSystemFont(ofSize: NSFont.systemFontSize, weight: .regular)) | ||
.onAppear { | ||
loadContent() | ||
} | ||
} | ||
|
||
private func loadContent() { | ||
// (....) | ||
self.text = AttributedString(string) | ||
} | ||
} | ||
``` | ||
|
||
<img width="612" alt="Default Theme" src="https://github.com/krzyzanowskim/STTextView-Plugin-Neon/assets/758033/03c35889-da7f-48c1-8982-77430eb69a20"> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import Cocoa | ||
import STTextView | ||
|
||
import Neon | ||
import TreeSitterClient | ||
import SwiftTreeSitter | ||
|
||
import CodeEditLanguages | ||
|
||
public class Coordinator { | ||
private(set) var highlighter: Neon.Highlighter? | ||
private let language: CodeLanguage | ||
private let tsLanguage: SwiftTreeSitter.Language | ||
private let tsClient: TreeSitterClient | ||
private var prevViewportRange: NSTextRange? | ||
|
||
init(textView: STTextView, theme: Theme, language: CodeLanguage) { | ||
tsLanguage = language.language! | ||
self.language = language | ||
|
||
tsClient = try! TreeSitterClient(language: tsLanguage) { codePointIndex in | ||
guard let location = textView.textContentManager.location(at: codePointIndex), | ||
let position = textView.textContentManager.position(location) | ||
else { | ||
return .zero | ||
} | ||
|
||
return Point(row: position.row, column: position.column) | ||
} | ||
|
||
|
||
tsClient.invalidationHandler = { [weak self] indexSet in | ||
DispatchQueue.main.async { | ||
self?.highlighter?.invalidate(.set(indexSet)) | ||
} | ||
} | ||
|
||
// set textview default font to theme default font | ||
textView.font = theme.tokens[.default]?.font?.value ?? textView.font | ||
|
||
DispatchQueue.main.async { | ||
self.highlighter = Neon.Highlighter(textInterface: STTextViewSystemInterface(textView: textView) { neonToken in | ||
var attributes: [NSAttributedString.Key: Any] = [:] | ||
if let tvFont = textView.font { | ||
attributes[.font] = tvFont | ||
} | ||
|
||
if let themeValue = theme.tokens[TokenName(neonToken.name)] { | ||
attributes[.foregroundColor] = themeValue.color.value | ||
|
||
if let font = themeValue.font?.value { | ||
attributes[.font] = font | ||
} | ||
} else if let themeValue = theme.tokens[.default]{ | ||
attributes[.foregroundColor] = themeValue.color.value | ||
|
||
if let font = themeValue.font?.value { | ||
attributes[.font] = font | ||
} | ||
} | ||
|
||
return !attributes.isEmpty ? attributes : nil | ||
}, tokenProvider: self.tokenProvider(textContentManager: textView.textContentManager)) | ||
} | ||
|
||
// initial parse of the whole content | ||
tsClient.willChangeContent(in: NSRange(textView.textContentManager.documentRange, in: textView.textContentManager)) | ||
tsClient.didChangeContent(in: NSRange(textView.textContentManager.documentRange, in: textView.textContentManager), delta: textView.textContentManager.length, limit: textView.textContentManager.length, readHandler: Parser.readFunction(for: textView.textContentManager.attributedString(in: nil)?.string ?? ""), completionHandler: {}) | ||
} | ||
|
||
private func tokenProvider(textContentManager: NSTextContentManager) -> Neon.TokenProvider? { | ||
guard let highlightsQuery = try? tsLanguage.query(contentsOf: language.queryURL!) else { | ||
return nil | ||
} | ||
|
||
return tsClient.tokenProvider(with: highlightsQuery) { range, _ in | ||
guard range.isEmpty == false else { return nil } | ||
return textContentManager.attributedString(in: NSTextRange(range, provider: textContentManager))?.string | ||
} | ||
} | ||
|
||
func updateViewportRange(_ range: NSTextRange?) { | ||
if range != prevViewportRange { | ||
DispatchQueue.main.async { | ||
self.highlighter?.visibleContentDidChange() | ||
} | ||
} | ||
prevViewportRange = range | ||
} | ||
|
||
func willChangeContent(in range: NSRange) { | ||
tsClient.willChangeContent(in: range) | ||
} | ||
|
||
func didChangeContent(_ textContentManager: NSTextContentManager, in range: NSRange, delta: Int, limit: Int) { | ||
/// TODO: Instead get the *whole* string over and over (can be expensive for large documents) | ||
/// implement maybe a reader function that read what needed only (is it possible?) | ||
if let str = textContentManager.attributedString(in: nil)?.string { | ||
let readFunction = Parser.readFunction(for: str) | ||
tsClient.didChangeContent(in: range, delta: delta, limit: limit, readHandler: readFunction, completionHandler: {}) | ||
} | ||
|
||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
Sources/NeonCodeEditLanguagesPlugin/NeonCodeEditLanguagesPlugin.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Cocoa | ||
|
||
import STTextView | ||
|
||
import CodeEditLanguages | ||
|
||
public struct NeonCodeEditLanguagesPlugin: STPlugin { | ||
private let theme: Theme | ||
private let language: CodeLanguage | ||
|
||
public init(theme: Theme = .default, language: CodeLanguage) { | ||
self.theme = theme | ||
self.language = language | ||
} | ||
|
||
public func setUp(context: any Context) { | ||
|
||
context.events.onWillChangeText { affectedRange in | ||
let range = NSRange(affectedRange, in: context.textView.textContentManager) | ||
context.coordinator.willChangeContent(in: range) | ||
} | ||
|
||
context.events.onDidChangeText { affectedRange, replacementString in | ||
guard let replacementString else { return } | ||
|
||
let range = NSRange(affectedRange, in: context.textView.textContentManager) | ||
context.coordinator.didChangeContent(context.textView.textContentManager, in: range, delta: replacementString.utf16.count - range.length, limit: context.textView.textContentManager.length) | ||
} | ||
|
||
context.events.onDidLayoutViewport { viewportRange in | ||
context.coordinator.updateViewportRange(viewportRange) | ||
} | ||
} | ||
|
||
public func makeCoordinator(context: CoordinatorContext) -> Coordinator { | ||
Coordinator(textView: context.textView, theme: theme, language: language) | ||
} | ||
|
||
} | ||
|
53 changes: 53 additions & 0 deletions
53
Sources/NeonCodeEditLanguagesPlugin/STTextViewSystemInterface.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import Cocoa | ||
import STTextView | ||
import Neon | ||
|
||
class STTextViewSystemInterface: TextSystemInterface { | ||
|
||
typealias AttributeProvider = (Neon.Token) -> [NSAttributedString.Key: Any]? | ||
|
||
private let textView: STTextView | ||
private let attributeProvider: AttributeProvider | ||
|
||
init(textView: STTextView, attributeProvider: @escaping AttributeProvider) { | ||
self.textView = textView | ||
self.attributeProvider = attributeProvider | ||
} | ||
|
||
func clearStyle(in range: NSRange) { | ||
guard let textRange = NSTextRange(range, in: textView.textContentManager) else { | ||
assertionFailure() | ||
return | ||
} | ||
|
||
textView.textLayoutManager.removeRenderingAttribute(.foregroundColor, for: textRange) | ||
if let defaultFont = textView.font { | ||
textView.addAttributes([.font: defaultFont], range: range) | ||
} | ||
} | ||
|
||
func applyStyle(to token: Neon.Token) { | ||
print(token) | ||
guard let attrs = attributeProvider(token), | ||
let textRange = NSTextRange(token.range, in: textView.textContentManager) | ||
else { | ||
return | ||
} | ||
|
||
for attr in attrs { | ||
textView.textLayoutManager.addRenderingAttribute(attr.key, value: attr.value, for: textRange) | ||
} | ||
} | ||
|
||
var length: Int { | ||
textView.textContentManager.length | ||
} | ||
|
||
var visibleRange: NSRange { | ||
guard let viewportRange = textView.textLayoutManager.textViewportLayoutController.viewportRange else { | ||
return .zero | ||
} | ||
|
||
return NSRange(viewportRange, provider: textView.textContentManager) | ||
} | ||
} |
Oops, something went wrong.