splash-rs/highlight-swift/Sources/highlight-swift/highlight_swift.swift

121 lines
3.8 KiB
Swift

import Foundation
import Splash
@_cdecl("highlight_swift")
public func highlight(codePtr: UnsafeRawPointer, codeLen: UInt64, outPtr: UnsafeMutableRawPointer, maxLen: UInt64) -> UInt64 {
// don't free, the underlying data is owned by rust
let code = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: codePtr), length: Int(codeLen), encoding: .utf8, freeWhenDone: false)!
let highligher = SyntaxHighlighter(format: MyOutputFormat())
var html = highligher.highlight(code)
precondition(html.utf8.count <= maxLen)
return html.withUTF8 { buf in
buf.copyBytes(to: UnsafeMutableRawBufferPointer(start: outPtr, count: Int(maxLen)))
return UInt64(buf.count)
}
}
struct MyOutputFormat: OutputFormat {
func makeBuilder() -> Builder {
return Builder()
}
struct Builder: OutputBuilder {
typealias Output = String
private var html = ""
private var pendingToken: (string: String, type: TokenType)?
private var pendingWhitespace: String?
mutating func addToken(_ token: String, ofType type: TokenType) {
if var pendingToken = pendingToken {
guard pendingToken.type != type else {
pendingWhitespace.map { pendingToken.string += $0 }
pendingWhitespace = nil
pendingToken.string += token
self.pendingToken = pendingToken
return
}
}
appendPending()
pendingToken = (token, type)
}
mutating func addPlainText(_ text: String) {
appendPending()
html.append(contentsOf: text.escapingHTMLEntities())
}
mutating func addWhitespace(_ whitespace: String) {
if pendingToken != nil {
pendingWhitespace = (pendingWhitespace ?? "") + whitespace
} else {
html.append(whitespace)
}
}
mutating func build() -> String {
appendPending()
return html
}
private mutating func appendPending() {
if let pendingToken = pendingToken {
let cls: String
switch pendingToken.type {
case .keyword:
cls = "hl-kw"
case .string:
cls = "hl-str"
case .type:
cls = "hl-type"
case .call:
cls = "hl-fn"
case .number:
cls = "hl-num"
case .comment:
cls = "hl-cmt"
case .property:
cls = "hl-var"
case .dotAccess:
cls = "hl-prop"
case .preprocessing:
cls = ""
case .custom(_):
cls = ""
}
html.append("""
<span class="\(cls)">\(pendingToken.string.escapingHTMLEntities())</span>
""")
self.pendingToken = nil
}
if let pendingWhitespace = pendingWhitespace {
html.append(pendingWhitespace)
self.pendingWhitespace = nil
}
}
}
}
// copied from Splash, where it's internal
extension StringProtocol {
func escapingHTMLEntities() -> String {
return String(flatMap { character -> String in
switch character {
case "&":
return "&amp;"
case "<":
return "&lt;"
case ">":
return "&gt;"
default:
return String(character)
}
})
}
}