121 lines
3.8 KiB
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 "&"
|
|
case "<":
|
|
return "<"
|
|
case ">":
|
|
return ">"
|
|
default:
|
|
return String(character)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|