import Foundation public struct EditableRegion: Hashable, Sendable { public var lineIndexes: [Int] public init(lineIndexes: some Sequence) { self.lineIndexes = Array(Set(lineIndexes)).sorted() } public var primaryLineIndex: Int { lineIndexes.first ?? -1 } public var isEmpty: Bool { lineIndexes.isEmpty } public func contains(_ lineIndex: Int) -> Bool { lineIndexes.binarySearch(lineIndex) } public static func none() -> EditableRegion { EditableRegion(lineIndexes: []) } public static func selection(_ selection: NSRange, in lineIndex: DocumentLineIndex) -> EditableRegion { guard lineIndex.lineCount > 0 else { return .none() } let sourceLength = lineIndex.source.utf16.count let startLocation = max(0, min(selection.location, sourceLength)) let endLocation: Int if selection.length == 0 { endLocation = startLocation } else { endLocation = max(startLocation, min(selection.upperBound - 1, sourceLength)) } let startLine = lineIndex.lineIndex(containing: startLocation) let endLine = lineIndex.lineIndex(containing: endLocation) let selectedRange = min(startLine, endLine)...max(startLine, endLine) var editableLines = Set(selectedRange) guard lineIndex.source.contains("```") || lineIndex.source.contains("~~~") else { return EditableRegion(lineIndexes: editableLines) } for selectedLine in selectedRange { if let codeBlockRange = lineIndex.fencedCodeBlockLineRange(containing: selectedLine) { editableLines.formUnion(codeBlockRange) } } return EditableRegion(lineIndexes: editableLines) } } private extension DocumentLineIndex { func fencedCodeBlockLineRange(containing lineIndex: Int) -> ClosedRange? { guard (0.. String? { let indentation = source.prefix { $0 == " " || $0 == "\t" } guard indentation.count <= 3 else { return nil } let content = source.dropFirst(indentation.count) if content.hasPrefix("```") { return "```" } if content.hasPrefix("~~~") { return "~~~" } return nil } } private extension Array where Element == Int { func binarySearch(_ value: Int) -> Bool { var lowerBound = 0 var upperBound = count - 1 while lowerBound <= upperBound { let midpoint = (lowerBound + upperBound) / 2 if self[midpoint] == value { return true } if self[midpoint] < value { lowerBound = midpoint + 1 } else { upperBound = midpoint - 1 } } return false } } private extension NSRange { var upperBound: Int { location + length } }