Skip to content

Commit

Permalink
improve CodeText usability and performance
Browse files Browse the repository at this point in the history
  • Loading branch information
appstefan committed Jun 25, 2024
1 parent 3d2a1e5 commit 49a85e0
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 164 deletions.
168 changes: 115 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# HighlightSwift 🎨
# HighlightSwift

![](https://img.shields.io/github/v/release/appstefan/highlightswift)
![](https://img.shields.io/github/license/appstefan/highlightswift)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fappstefan%2FHighlightSwift%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/appstefan/HighlightSwift)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fappstefan%2FHighlightSwift%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/appstefan/HighlightSwift)

Syntax Highlighting in Swift and SwiftUI
Syntax Highlighting for Swift and SwiftUI

<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/appstefan/HighlightSwift/assets/6455394/892a5be0-146e-4cb8-90ca-559c2c67452c">
Expand All @@ -16,7 +16,7 @@ Syntax Highlighting in Swift and SwiftUI
## Contents

#### `Highlight`
Convert any `String` of code into a syntax highlighted `AttributedString`
Swift class to convert a `String` of code into a syntax highlighted `AttributedString`
* 🔍 Automatic language detection
* 📚 Support for 50+ common languages
* 🌈 Choose from 30 built-in color themes or use custom CSS
Expand All @@ -25,10 +25,12 @@ Convert any `String` of code into a syntax highlighted `AttributedString`
* 🖥️ Works on iOS, iPadOS, macOS, and tvOS

#### `CodeText`
Display syntax highlighted code just like a standard `Text` view
SwiftUI view to display a `String` of code with syntax highlighting
* 🌗 Color theme syncs automatically with Dark Mode
* 🔠 Supports text modifiers like `.bold()` or `.font()`
* 🟩 Optional customizable `.card` style includes the theme background color
* 📜 Theme background color included with `.card` style
* 🔠 Works with `Text` modifiers like `.bold()` or `.font()`
* ⚙️ Includes modifiers to set the color theme, style and language
* 📫 Callback modifiers to get the highlight results

<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/appstefan/HighlightSwift/assets/6455394/5021a822-39f2-40bd-b1f8-2680c2382dd3">
Expand All @@ -38,95 +40,155 @@ Display syntax highlighted code just like a standard `Text` view

## Highlight

Create an instance of the `Highlight` class:
Create an instance of `Highlight` and convert a `String` of code into a syntax highlighted `AttributedString`:
```swift
@State var highlight = Highlight()
let someCode = """
print(\"Hello World\")
"""
let highlight = Highlight()
let attributedText = try await highlight.attributedText(someCode)
```

Convert a `String` of code into a syntax highlighted `AttributedString`:
Add the `language:` parameter to bypass automatic language detection:
```swift
let attributedText = try await highlight.attributedText("print(\"Hello World\")")
let attributedText = try await highlight.attributedText(someCode, language: "swift")
```

Providing the `language:` parameter disables automatic language detection:
Use the `colors:` parameter to change the color theme.
```swift
let attributedText = try await highlight.attributedText(code, language: "swift")
let attributedText = try await highlight.attributedText(someCode, colors: .dark(.github))
```

Set the `colors:` parameter to choose the highlight color theme.
Apply a custom CSS theme with the `.custom` option.
Refer to the highlight.js [Theme Guide](https://highlightjs.readthedocs.io/en/latest/theme-guide.html#) for details:
```swift
let attributedText = try await highlight.attributedText(code, colors: .dark(.github))
```

Or use any custom CSS theme with the `.custom` option.
Refer to the highlight.js [Theme Guide](https://highlightjs.readthedocs.io/en/latest/theme-guide.html#) for more info.
```swift
let attributedText = try await highlight.attributedText(code, colors: .custom(css: someCoolCSS))
let someCSS = """
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
}
"""
let attributedText = try await highlight.attributedText(someCode, colors: .custom(css: someCSS))
```

The `request` function returns a `HighlightResult` struct.
This result struct includes details such as the detected language along with the attributed text:
```swift
let result: HighlightResult = try await highlight.request("print(\"Hello World\")")

// HighlightResult(
// attributedText: "...",
// relevance: 5,
// language: "swift",
// languageName: "Swift?",
// backgroundColor: #1F2024FF,
// hasIllegal: false,
// isUndefined: false)
let result: HighlightResult = try await highlight.request(someCode)
print(result)
```
```swift
HighlightResult(
attributedText: "...",
relevance: 5,
language: "swift",
languageName: "Swift?",
backgroundColor: #1F2024FF,
hasIllegal: false,
isUndefined: false)
```

##
### `CodeText`

Create a `CodeText` view:
Create a `CodeText` view with some code:
```swift
CodeText("print(\"Hello World\")")
let someCode: String = """
print(\"Hello World\")
"""

var body: some View {
CodeText(someCode)
}
```

The default style is `.plain` with no background or padding.
Use the customizable `.card` style to show the theme background color:
Add the `.codeTextColors(_:)` modifier to set the color theme.
The built-in color themes update automatically with Dark Mode to the corresponding dark variant.
```swift
CodeText("print(\"Hello World\")")
.codeTextStyle(.card(cornerRadius: 5))
CodeText(someCode)
.codeTextColors(.github)
```

Apply standard `Text` modifiers like `.font()`:
The default style is `.plain` without any background or padding.
Some of the color themes are more legible with their corresponding background color.
Add the `.codeTextStyle(_:)` modifier and choose the `.card` style to show the background:
```swift
CodeText("print(\"Hello World\")")
.font(.system(.callout, weight: .semibold))
CodeText(someCode)
.codeTextStyle(.card)
```

Add the `.codeTextColors(_:)` modifier to set the color theme.
The built-in color themes update automatically with Dark Mode to the corresponding dark variant.
The `.card` style has a few customization options, for example:
```swift
CodeText("print(\"Hello World\")")
.codeTextColors(.github)
CodeText(someCode)
.codeTextStyle(.card(cornerRadius: 0, stroke: .separator, verticalPadding: 12))
```

For more control, use any custom CSS theme with the `.custom` color option.
Adjust using standard `Text` modifiers like `.font()`:
```swift
CodeText(someCode)
.font(.callout)
.fontWeight(.semibold)
```

Choose the `.custom` option to use any custom CSS color theme.
Refer to the official highlight.js [Theme Guide](https://highlightjs.readthedocs.io/en/latest/theme-guide.html#) for more info.
```swift
CodeText("print(\"Hello World\")")
CodeText(someCode)
.codeTextColors(.custom(dark: .custom(css: someDarkCSS), light: .custom(css: someLightCSS)))
```

Use the `.codeTextLanguage(_:)` modifier to set a specific language and disable automatic detection:
Add the `.highlightLanguage(_:)` modifier to set a language and bypass automatic detection:
```swift
CodeText("print(\"Hello World\")")
.codeTextLanguage(.swift)
CodeText(someCode)
.highlightLanguage(.swift)
```

Add the `.onHighlight(_:)` modifier to get the detected language, background color and other details:
Add `.onHighlightSuccess(_:)` to get the highlight results, including the detected language, relevancy score, background color and other details. Errors are unlikely but can be handled with `.onHighlightFailure(_:)` if necessary.
```swift
var body: some View {
CodeText("print(\"Hello World\")")
.onHighlight { result in
// ...
CodeText(someCode)
.onHighlightSuccess { result in
...
}
.onHighlightFailure { error in
...
}
```

Note that failing to match a language to the input is not considered a highlight failure.
Rather, the result will have `isUndefined` set to `true` and the language will be "unknown" with a relevance score of zero.

There is also a combined `.onHighlightResult(_:)` equivalent of the two callbacks above.
```swift
CodeText(someCode)
.onHighlightResult { result in
switch result {
case .success:
...
case .failure:
...
}
}
```

A previously stored highlight result can also be passed to the `CodeText`.
This can help in places where it reappears often, such as in a list view.
```swift
let someCode: String = """
print(\"Hello World\")
"""

@State var result: HighlightResult?

var body: some View {
List {
...
CodeText(someCode, result: result)
.onHighlightSuccess { result in
self.result = result
}
...
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@ public struct CodeText {
internal var mode: HighlightMode = .automatic
internal var style: CodeTextStyle = .plain
internal var colors: CodeTextColors = .theme(.xcode)
internal var onHighlight: ((Result<HighlightResult, Error>) -> Void)?

@State internal var highlight = Highlight()
internal var success: ((HighlightResult) -> Void)?
internal var failure: ((Error) -> Void)?
internal var result: ((Result<HighlightResult, Error>) -> Void)?

@State internal var highlightTask: Task<Void, Never>?
@State internal var highlightResult: HighlightResult?

@Environment(\.highlight) internal var highlight
@Environment(\.colorScheme) internal var colorScheme

/// Creates a text view that displays syntax highlighted code.
/// - Parameters:
/// - text: Plain text code to be syntax highlighted and shown.
public init(_ text: String) {
/// - text: Plain text code to be syntax highlighted and displayed.
/// - result: Existing highlight result to display instead of highlighting the text on appear.
public init(_ text: String, result: HighlightResult? = nil) {
self.text = text
self._highlightResult = .init(initialValue: result)
}

internal var attributedText: AttributedString {
Expand All @@ -30,19 +35,21 @@ public struct CodeText {
internal func highlightText(
mode: HighlightMode? = nil,
colors: CodeTextColors? = nil,
colorScheme: ColorScheme? = nil)
async {
colorScheme: ColorScheme? = nil
) async {
let text = self.text
let mode = mode ?? self.mode
let colors = colors ?? self.colors
let scheme = colorScheme ?? self.colorScheme
let schemeColors = scheme == .dark ? colors.dark : colors.light
do {
let result = try await highlight.request(text, mode: mode, colors: schemeColors)
highlightResult = result
onHighlight?(.success(result))
let highlightResult = try await highlight.request(text, mode: mode, colors: schemeColors)
self.highlightResult = highlightResult
result?(.success(highlightResult))
success?(highlightResult)
} catch {
onHighlight?(.failure(error))
result?(.failure(error))
failure?(error)
}
}
}
17 changes: 17 additions & 0 deletions Sources/HighlightSwift/CodeText/CodeTextCardView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftUI

struct CodeTextCardView: View {
let style: CardCodeTextStyle
let color: Color?

var body: some View {
ZStack {
if let color {
RoundedRectangle(cornerRadius: style.cornerRadius, style: style.cornerStyle)
.fill(color)
}
RoundedRectangle(cornerRadius: style.cornerRadius, style: style.cornerStyle)
.stroke(AnyShapeStyle(style.stroke), lineWidth: style.lineWidth)
}
}
}
53 changes: 53 additions & 0 deletions Sources/HighlightSwift/CodeText/CodeTextModifiers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@available(iOS 16.1, tvOS 16.1, *)
extension CodeText {
/// Sets the code text style for this code text.
public func codeTextStyle<S>(_ style: S) -> CodeText where S : CodeTextStyle {
var content = self
content.style = style
return content
}

/// Sets the highlight color theme for this code text.
public func codeTextColors(_ colors: CodeTextColors) -> CodeText {
var content = self
content.colors = colors
return content
}

/// Sets the language detection mode for this code text.
/// See also `highlightLanguage(_:)` to simply set a language.
public func highlightMode(_ mode: HighlightMode) -> CodeText {
var content = self
content.mode = mode
return content
}

/// Sets the language for this code text.
/// Equivalent to `.highlightMode(.language(...))`
public func highlightLanguage(_ language: HighlightLanguage) -> CodeText {
var content = self
content.mode = .language(language)
return content
}

/// Adds an action to perform after the text has been highlighted or an error occurs.
public func onHighlightResult(_ perform: @escaping ((Result<HighlightResult, Error>) -> Void)) -> CodeText {
var content = self
content.result = perform
return content
}

/// Adds an action to perform if an error occurs while highlighting.
public func onHighlightFailure(_ perform: @escaping ((Error) -> Void)) -> CodeText {
var content = self
content.failure = perform
return content
}

/// Adds an action to perform after the text has been highlighted.
public func onHighlightSuccess(_ perform: @escaping ((HighlightResult) -> Void)) -> CodeText {
var content = self
content.success = perform
return content
}
}
Loading

0 comments on commit 49a85e0

Please sign in to comment.