-
-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Adds default tab stops * Fixes SwiftLint warning * Adds StringSyntaxHighlighter * Adds documentation * Fixes SwiftLint warnings * Improves formatting
- Loading branch information
Showing
4 changed files
with
200 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
45 changes: 45 additions & 0 deletions
45
Sources/Runestone/Documentation.docc/Extensions/StringSyntaxHighlighter.md
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,45 @@ | ||
# ``StringSyntaxHighlighter`` | ||
|
||
## Example | ||
|
||
Create a syntax highlighter by passing a theme and language, and then call the ``StringSyntaxHighlighter/syntaxHighlight(_:)`` method to syntax highlight the provided text. | ||
|
||
```swift | ||
let syntaxHighlighter = StringSyntaxHighlighter( | ||
theme: TomorrowTheme(), | ||
language: .javaScript | ||
) | ||
let attributedString = syntaxHighlighter.syntaxHighlight( | ||
""" | ||
function fibonacci(num) { | ||
if (num <= 1) { | ||
return 1 | ||
} | ||
return fibonacci(num - 1) + fibonacci(num - 2) | ||
} | ||
""" | ||
) | ||
``` | ||
|
||
## Topics | ||
|
||
### Essentials | ||
|
||
- <doc:SyntaxHighlightingAString> | ||
- ``StringSyntaxHighlighter/syntaxHighlight(_:)`` | ||
|
||
### Initialing the Syntax Highlighter | ||
|
||
- ``StringSyntaxHighlighter/init(theme:language:languageProvider:)`` | ||
|
||
### Configuring the Appearance | ||
|
||
- ``StringSyntaxHighlighter/theme`` | ||
- ``StringSyntaxHighlighter/kern`` | ||
- ``StringSyntaxHighlighter/lineHeightMultiplier`` | ||
- ``StringSyntaxHighlighter/tabLength`` | ||
|
||
### Specifying the Language | ||
|
||
- ``StringSyntaxHighlighter/language`` | ||
- ``StringSyntaxHighlighter/languageProvider`` |
48 changes: 48 additions & 0 deletions
48
Sources/Runestone/Documentation.docc/SyntaxHighlightingAString.md
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,48 @@ | ||
# Syntax Highlighting a String | ||
|
||
Learn how to syntax hightlight a string without needing to create a TextView. | ||
|
||
## Overview | ||
|
||
The <doc:StringSyntaxHighlighter> can be used to syntax highlight a string without needing to create a <doc:TextView>. | ||
|
||
Before reading this article, make sure that you have follow the guides on <doc:AddingATreeSitterLanguage> and <doc:CreatingATheme>. | ||
|
||
|
||
## Creating an Attributed String | ||
|
||
Create an instance of <doc:StringSyntaxHighlighter> by supplying the theme containing the colors and fonts to be used for syntax highlighting the text, as well as the language to use when parsing the text. | ||
|
||
```swift | ||
let syntaxHighlighter = StringSyntaxHighlighter( | ||
theme: TomorrowTheme(), | ||
language: .javaScript | ||
) | ||
``` | ||
|
||
If the language has any embedded languages, you will need to pass an object conforming to <doc:TreeSitterLanguageProvider>, which provides the syntax highlighter with additional languages. | ||
|
||
Apply customizations to the syntax highlighter as needed. | ||
|
||
```swift | ||
syntaxHighlighter.kern = 0.3 | ||
syntaxHighlighter.lineHeightMultiplier = 1.2 | ||
syntaxHighlighter.tabLength = 2 | ||
``` | ||
|
||
With the syntax highlighter created and configured, we can syntax highlight the text. | ||
|
||
```swift | ||
let attributedString = syntaxHighlighter.syntaxHighlight( | ||
""" | ||
function fibonacci(num) { | ||
if (num <= 1) { | ||
return 1 | ||
} | ||
return fibonacci(num - 1) + fibonacci(num - 2) | ||
} | ||
""" | ||
) | ||
``` | ||
|
||
The attributed string can be displayed using a UILabel or UITextView. |
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,105 @@ | ||
import UIKit | ||
|
||
/// Syntax highlights a string. | ||
/// | ||
/// An instance of `StringSyntaxHighlighter` can be used to syntax highlight a string without needing to create a `TextView`. | ||
public final class StringSyntaxHighlighter { | ||
/// The theme to use when syntax highlighting the text. | ||
public var theme: Theme | ||
/// The language to use when parsing the text. | ||
public var language: TreeSitterLanguage | ||
/// Object that can provide embedded languages on demand. A strong reference will be stored to the language provider. | ||
public var languageProvider: TreeSitterLanguageProvider? | ||
/// The number of points by which to adjust kern. | ||
/// | ||
/// The default value is 0 meaning that kerning is disabled. | ||
public var kern: CGFloat = 0 | ||
/// The tab length determines the width of the tab measured in space characers. | ||
/// | ||
/// The default value is 4 meaning that a tab is four spaces wide. | ||
public var tabLength: Int = 4 | ||
/// The line-height is multiplied with the value. | ||
public var lineHeightMultiplier: CGFloat = 1 | ||
|
||
/// Creates an object that can syntax highlight a text. | ||
/// - Parameters: | ||
/// - theme: The theme to use when syntax highlighting the text. | ||
/// - language: The language to use when parsing the text | ||
/// - languageProvider: Object that can provide embedded languages on demand. A strong reference will be stored to the language provider.. | ||
public init( | ||
theme: Theme = DefaultTheme(), | ||
language: TreeSitterLanguage, | ||
languageProvider: TreeSitterLanguageProvider? = nil | ||
) { | ||
self.theme = theme | ||
self.language = language | ||
self.languageProvider = languageProvider | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
/// Syntax highlights the text using the configured syntax highlighter. | ||
/// - Parameter text: The text to be syntax highlighted. | ||
/// - Returns: An attributed string containing the syntax highlighted text. | ||
public func syntaxHighlight(_ text: String) -> NSAttributedString { | ||
let mutableString = NSMutableString(string: text) | ||
let stringView = StringView(string: mutableString) | ||
let lineManager = LineManager(stringView: stringView) | ||
lineManager.rebuild() | ||
let languageMode = TreeSitterLanguageMode(language: language, languageProvider: languageProvider) | ||
let internalLanguageMode = languageMode.makeInternalLanguageMode( | ||
stringView: stringView, | ||
lineManager: lineManager | ||
) | ||
internalLanguageMode.parse(mutableString) | ||
let tabWidth = TabWidthMeasurer.tabWidth(tabLength: tabLength, font: theme.font) | ||
let mutableAttributedString = NSMutableAttributedString(string: text) | ||
let defaultAttributes = DefaultStringAttributes( | ||
textColor: theme.textColor, | ||
font: theme.font, | ||
kern: kern, | ||
tabWidth: tabWidth | ||
) | ||
defaultAttributes.apply(to: mutableAttributedString) | ||
applyLineHeightMultiplier(to: mutableAttributedString) | ||
let byteRange = ByteRange(from: 0, to: text.byteCount) | ||
let syntaxHighlighter = internalLanguageMode.createLineSyntaxHighlighter() | ||
syntaxHighlighter.theme = theme | ||
let syntaxHighlighterInput = LineSyntaxHighlighterInput( | ||
attributedString: mutableAttributedString, | ||
byteRange: byteRange | ||
) | ||
syntaxHighlighter.syntaxHighlight(syntaxHighlighterInput) | ||
return mutableAttributedString | ||
} | ||
} | ||
|
||
private extension StringSyntaxHighlighter { | ||
private func applyLineHeightMultiplier(to attributedString: NSMutableAttributedString) { | ||
let scaledLineHeight = theme.font.totalLineHeight * lineHeightMultiplier | ||
let mutableParagraphStyle = getMutableParagraphStyle(from: attributedString) | ||
mutableParagraphStyle.lineSpacing = scaledLineHeight - theme.font.totalLineHeight | ||
let range = NSRange(location: 0, length: attributedString.length) | ||
attributedString.beginEditing() | ||
attributedString.removeAttribute(.paragraphStyle, range: range) | ||
attributedString.addAttribute(.paragraphStyle, value: mutableParagraphStyle, range: range) | ||
attributedString.endEditing() | ||
} | ||
|
||
private func getMutableParagraphStyle( | ||
from attributedString: NSMutableAttributedString | ||
) -> NSMutableParagraphStyle { | ||
guard let attributeValue = attributedString.attribute(.paragraphStyle, at: 0, effectiveRange: nil) else { | ||
return NSMutableParagraphStyle() | ||
} | ||
guard let paragraphStyle = attributeValue as? NSParagraphStyle else { | ||
fatalError("Expected .paragraphStyle attribute to be instance of NSParagraphStyle") | ||
} | ||
guard let mutableParagraphStyle = paragraphStyle.mutableCopy() as? NSMutableParagraphStyle else { | ||
fatalError("Expected mutableCopy() to return an instance of NSMutableParagraphStyle") | ||
} | ||
return mutableParagraphStyle | ||
} | ||
} |