import Foundation /// Text helpers that avoid `CharacterSet` and allocations for performance. enum TextHelper { /// Splits text into lines as slices. static func lineSlices(_ text: String) -> [Substring] { var result: [Substring] = [] enumerateLines(in: text) { line, _ in result.append(line) } return result } /// Invokes `body` once per line, passing a `Substring` view and its index. static func enumerateLines(in text: String, _ body: (Substring, Int) -> Void) { let utf8 = text.utf8 let end = utf8.endIndex var lineStart = utf8.startIndex var i = utf8.startIndex var index = 0 while i < end { if utf8[i] == 0x0A { body(text[lineStart.. String.Index { guard newline > start else { return newline } let last = utf8.index(before: newline) return utf8[last] == 0x0D ? last : newline } /// Returns the line at `index` (zero-based) as a `Substring`, or `nil` when /// there is no such line. Trailing `\r` is stripped and line counting matches /// ``lines(_:)`` / ``enumerateLines(in:_:)``. Stops as soon as the line is /// found, so locating an early line in a large document is cheap. static func line(in text: String, at index: Int) -> Substring? { guard index >= 0 else { return nil } let utf8 = text.utf8 let end = utf8.endIndex var lineStart = utf8.startIndex var i = utf8.startIndex var current = 0 while i < end { if utf8[i] == 0x0A { if current == index { return text[lineStart.. Substring { // Trim over the UTF-8 view: Norg indentation and trailing space is always // ASCII whitespace, whose bytes are all `< 0x80` and so never part of a // multi-byte scalar — the trimmed bounds stay on scalar boundaries. This // avoids the grapheme decode `Character.isWhitespace` pays on every line. let utf8 = s.utf8 var start = utf8.startIndex var end = utf8.endIndex while start < end, ASCIIHelper.isWhitespace(utf8[start]) { start = utf8.index(after: start) } while start < end { let prev = utf8.index(before: end) guard ASCIIHelper.isWhitespace(utf8[prev]) else { break } end = prev } return s[start.. String { let trimmed = whitespaceTrimmed(s[...]) return trimmed.startIndex == s.startIndex && trimmed.endIndex == s.endIndex ? s : String(trimmed) } }