import SwiftUI /// Paints the ranges the server gives: `name:start,length,…;…`, each start /// counted from the end of the one before, over lines taken as joined by /// newlines, in UTF-16 units as the pages read them (`web/src/lib/highlight.ts`). enum Highlight { private static let colors: [String: Color] = [ "comment": Color(themed: { $0.syntax["comment"] ?? 0 }, light: 0x59636e, dark: 0x9198a1), "string": Color(themed: { $0.syntax["string"] ?? 0 }, light: 0x0a3069, dark: 0xa5d6ff), "keyword": Color(themed: { $0.syntax["keyword"] ?? 0 }, light: 0xcf222e, dark: 0xff7b72), "number": Color(themed: { $0.syntax["number"] ?? 0 }, light: 0x0550ae, dark: 0x79c0ff), "function": Color(themed: { $0.syntax["function"] ?? 0 }, light: 0x6639ba, dark: 0xd2a8ff), "type": Color(themed: { $0.syntax["type"] ?? 0 }, light: 0x953800, dark: 0xffa657), "tag": Color(themed: { $0.syntax["tag"] ?? 0 }, light: 0x116329, dark: 0x7ee787), "attr": Color(themed: { $0.syntax["attr"] ?? 0 }, light: 0x0550ae, dark: 0x79c0ff), ] static func paint(lines: [String], ranges: String?) -> [AttributedString] { var painted = lines.map { AttributedString($0) } guard let ranges, !ranges.isEmpty else { return painted } var starts: [Int] = [] var at = 0 for line in lines { starts.append(at) at += line.utf16.count + 1 } for group in ranges.split(separator: ";") { let parts = group.split(separator: ":", maxSplits: 1) guard parts.count == 2 else { continue } let name = String(parts[0]) let x = parts[1].split(separator: ",").compactMap { Int($0) } var offset = 0 var line = 0 var i = 0 while i + 1 < x.count { offset += x[i] let end = offset + x[i + 1] while line < lines.count, starts[line] + lines[line].utf16.count <= offset { line += 1 } var k = line while k < lines.count, starts[k] < end { apply(name, in: &painted[k], text: lines[k], from: max(offset, starts[k]) - starts[k], to: min(end, starts[k] + lines[k].utf16.count) - starts[k]) k += 1 } offset = end i += 2 } } return painted } private static func apply(_ name: String, in attributed: inout AttributedString, text: String, from: Int, to: Int) { guard from < to, let strings = Range(NSRange(location: from, length: to - from), in: text), let range = Range(strings, in: attributed) else { return } switch name { case "xins": attributed[range].backgroundColor = Color.success.opacity(0.3) case "xdel": attributed[range].backgroundColor = Color.danger.opacity(0.3) default: if let color = colors[name] { attributed[range].foregroundColor = color } } } }