Skip to content

Commit

Permalink
Move more API to LineEnding library
Browse files Browse the repository at this point in the history
  • Loading branch information
1024jp committed Jul 15, 2024
1 parent ef6f60d commit eb7a9c6
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 45 deletions.
18 changes: 3 additions & 15 deletions CotEditor/Sources/LineEndingScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,7 @@ import ValueRange
/// The line endings mostly occurred in the storage.
var majorLineEnding: LineEnding? {

Dictionary(grouping: self.lineEndings, by: \.value)
.sorted(\.value.first!.lowerBound)
.max { $0.value.count < $1.value.count }?
.key
self.lineEndings.majorValue()
}


Expand All @@ -104,17 +101,8 @@ import ValueRange

let string = self.textStorage.string.immutable

// expand range to scan by considering the possibility that a part of CRLF was edited
let nsString = string as NSString
let lowerScanBound: Int = (0..<editedRange.lowerBound).reversed().lazy
.prefix { [0xA, 0xD].contains(nsString.character(at: $0)) }
.last ?? editedRange.lowerBound
let upperScanBound: Int = (editedRange.upperBound..<nsString.length)
.prefix { [0xA, 0xD].contains(nsString.character(at: $0)) }
.last.flatMap { $0 + 1 } ?? editedRange.upperBound
let scanRange = NSRange(lowerScanBound..<upperScanBound)

let insertedLineEndings = string.lineEndingRanges(in: scanRange)
var scanRange: NSRange = .notFound
let insertedLineEndings = string.lineEndingRanges(in: editedRange, effectiveRange: &scanRange)
let inconsistentLineEndings = insertedLineEndings.filter { $0.value != self.baseLineEnding }

self.lineEndings.replace(items: insertedLineEndings, in: scanRange, changeInLength: delta)
Expand Down
13 changes: 13 additions & 0 deletions Packages/EditorCore/Sources/LineEnding/Collection+ValueRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,16 @@ public extension Array {
self.insert(contentsOf: items, at: lowerEditedIndex)
}
}


public extension Collection {

/// Returns the Value mostly occurred in the collection.
func majorValue<Value: Hashable>() -> Value? where Element == ValueRange<Value> {

Dictionary(grouping: self, by: \.value)
.sorted(using: KeyPathComparator(\.value.first?.lowerBound))
.max { $0.value.count < $1.value.count }?
.key
}
}
25 changes: 25 additions & 0 deletions Packages/EditorCore/Sources/LineEnding/String+LineEnding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ public extension String {

return lineEndingRanges
}


/// Collects ranges of all line endings with line ending types in the specified range.
///
/// This API can return line endings out of the specified range by considering the possibility
/// that the boundary of the specified range lies between CRLF.
///
/// - Parameters:
/// - range: The range to parse.
/// - effectiveRange: Upon return, the actual range of line endings collected.
/// - Returns: Ranges of line endings.
func lineEndingRanges(in range: NSRange, effectiveRange: inout NSRange) -> [ValueRange<LineEnding>] {

let nsString = self as NSString
let lowerScanBound: Int = (0..<range.lowerBound).reversed().lazy
.prefix { [0xA, 0xD].contains(nsString.character(at: $0)) }
.last ?? range.lowerBound
let upperScanBound: Int = (range.upperBound..<nsString.length)
.prefix { [0xA, 0xD].contains(nsString.character(at: $0)) }
.last.flatMap { $0 + 1 } ?? range.upperBound

effectiveRange = NSRange(lowerScanBound..<upperScanBound)

return self.lineEndingRanges(in: effectiveRange)
}
}


Expand Down
13 changes: 13 additions & 0 deletions Packages/EditorCore/Tests/LineEndingTests/CollectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,17 @@ struct CollectionTests {
array.firstIndex(where: { $0 > index }))
}
}


@Test func majorValue() {

#expect("".lineEndingRanges().majorValue() == nil)
#expect("a".lineEndingRanges().majorValue() == nil)
#expect("\n".lineEndingRanges().majorValue() == .lf)
#expect("\r".lineEndingRanges().majorValue() == .cr)
#expect("\r\n".lineEndingRanges().majorValue() == .crlf)
#expect("\u{85}".lineEndingRanges().majorValue() == .nel)
#expect("abc\u{2029}def".lineEndingRanges().majorValue() == .paragraphSeparator)
#expect("\rfoo\r\nbar\nbuz\u{2029}moin\r\n".lineEndingRanges().majorValue() == .crlf)
}
}
14 changes: 14 additions & 0 deletions Packages/EditorCore/Tests/LineEndingTests/LineEndingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ struct LineEndingTests {
}


@Test func lineEndingEffectiveRange() {

let string = "\r\n \n \r\n"
var range: NSRange = .notFound

#expect(string.lineEndingRanges(in: NSRange(1..<6), effectiveRange: &range) == [
.init(value: .crlf, location: 0),
.init(value: .lf, location: 3),
.init(value: .crlf, location: 5),
])
#expect(range == NSRange(0..<7))
}


@Test func replace() {

#expect("foo\r\nbar\n".replacingLineEndings(with: .cr) == "foo\rbar\r")
Expand Down
30 changes: 0 additions & 30 deletions Tests/LineEndingScannerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,36 +74,6 @@ struct LineEndingScannerTests {
[ValueRange(value: .crlf, range: NSRange(location: 3, length: 2)),
ValueRange(value: .cr, range: NSRange(location: 8, length: 1))])
}


@Test func detect() {

let storage = NSTextStorage()
let scanner = LineEndingScanner(textStorage: storage, lineEnding: .lf)

#expect(scanner.majorLineEnding == nil)

storage.string = "a"
#expect(scanner.majorLineEnding == nil)

storage.string = "\n"
#expect(scanner.majorLineEnding == .lf)

storage.string = "\r"
#expect(scanner.majorLineEnding == .cr)

storage.string = "\r\n"
#expect(scanner.majorLineEnding == .crlf)

storage.string = "\u{85}"
#expect(scanner.majorLineEnding == .nel)

storage.string = "abc\u{2029}def"
#expect(scanner.majorLineEnding == .paragraphSeparator)

storage.string = "\rfoo\r\nbar\nbuz\u{2029}moin\r\n"
#expect(scanner.majorLineEnding == .crlf) // most used new line must be detected
}
}


Expand Down

0 comments on commit eb7a9c6

Please sign in to comment.