mirror of
https://github.com/coteditor/CotEditor.git
synced 2024-09-19 07:07:18 +03:00
Add NSMutableAttributedString.truncateHead(until:offset:) method
This commit is contained in:
parent
cd9329f531
commit
f02e0c1ad8
@ -77,6 +77,31 @@ extension NSAttributedString {
|
||||
|
||||
|
||||
|
||||
extension NSMutableAttributedString {
|
||||
|
||||
/// Truncate head with an ellipsis symbol until the specific `location` if the length before the location is the longer than the `maxOffset`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - location: The character index to start truncation.
|
||||
/// - offset: The maximum number of composed characters to leave on the left of the `location`.
|
||||
func truncateHead(until location: Int, offset: Int) {
|
||||
|
||||
assert(location >= 0)
|
||||
assert(offset >= 0)
|
||||
|
||||
guard location > offset else { return }
|
||||
|
||||
let truncationIndex = (self.string as NSString)
|
||||
.lowerBoundOfComposedCharacterSequence(location, offsetBy: offset)
|
||||
moof(self.string, location, offset, truncationIndex)
|
||||
guard truncationIndex > 0 else { return }
|
||||
|
||||
self.replaceCharacters(in: NSRange(..<truncationIndex), with: "…")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension Sequence<NSAttributedString> {
|
||||
|
||||
/// Return a new attributed string by concatenating the elements of the sequence, adding the given separator between each element.
|
||||
|
@ -311,30 +311,29 @@ extension NSString {
|
||||
}
|
||||
|
||||
|
||||
/// Return the boundary of the composed character sequence by moving the offset by counting offset in composed character sequences.
|
||||
/// Return the lower bound of the composed character sequence by moving the bound in the head direction by counting offset in composed character sequences.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - index: The reference character index in UTF-16.
|
||||
/// - offset: The number of composed character sequences to move index.
|
||||
/// - Returns: A character index in UTF-16.
|
||||
func boundaryOfComposedCharacterSequence(_ index: Int, offsetBy offset: Int) -> Int {
|
||||
func lowerBoundOfComposedCharacterSequence(_ index: Int, offsetBy offset: Int) -> Int {
|
||||
|
||||
assert((0...self.length).contains(index))
|
||||
assert(offset >= 0)
|
||||
|
||||
let reverse = (offset <= 0)
|
||||
let range = reverse ? NSRange(location: 0, length: min(index + 1, self.length)) : NSRange(location: index, length: self.length - index)
|
||||
var options: EnumerationOptions = [.byComposedCharacterSequences, .substringNotRequired]
|
||||
if reverse {
|
||||
options.formUnion(.reverse)
|
||||
}
|
||||
if index == self.length, offset == 0 { return index }
|
||||
|
||||
var remainingCount = (index == self.length) ? offset : offset + 1
|
||||
var boundary = index
|
||||
var remainingCount = reverse ? -offset + 1 : offset
|
||||
|
||||
let range = NSRange(..<min(index + 1, self.length))
|
||||
let options: EnumerationOptions = [.byComposedCharacterSequences, .substringNotRequired, .reverse]
|
||||
self.enumerateSubstrings(in: range, options: options) { (_, range, _, stop) in
|
||||
|
||||
boundary = reverse ? range.lowerBound : range.upperBound
|
||||
|
||||
boundary = range.lowerBound
|
||||
remainingCount -= 1
|
||||
|
||||
if remainingCount <= 0 {
|
||||
stop.pointee = true
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// © 2020 1024jp
|
||||
// © 2020-2023 1024jp
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -53,6 +53,38 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
|
||||
func testTruncation() throws {
|
||||
|
||||
let string1 = NSMutableAttributedString(string: "0123456")
|
||||
string1.truncateHead(until: 5, offset: 2)
|
||||
XCTAssertEqual(string1.string, "…3456")
|
||||
|
||||
let string2 = NSMutableAttributedString(string: "0123456")
|
||||
string2.truncateHead(until: 2, offset: 3)
|
||||
XCTAssertEqual(string2.string, "0123456")
|
||||
|
||||
let string3 = NSMutableAttributedString(string: "🐱🐶🐮")
|
||||
string3.truncateHead(until: 4, offset: 1)
|
||||
XCTAssertEqual(string3.string, "…🐶🐮")
|
||||
|
||||
let string4 = NSMutableAttributedString(string: "🐈⬛🐕🐄")
|
||||
string4.truncateHead(until: 4, offset: 1)
|
||||
XCTAssertEqual(string4.string, "🐈⬛🐕🐄")
|
||||
|
||||
let string5 = NSMutableAttributedString(string: "🐈⬛🐕🐄")
|
||||
string5.truncateHead(until: 5, offset: 1)
|
||||
XCTAssertEqual(string5.string, "🐈⬛🐕🐄")
|
||||
|
||||
let string6 = NSMutableAttributedString(string: "🐈⬛ab")
|
||||
string6.truncateHead(until: 5, offset: 1)
|
||||
XCTAssertEqual(string6.string, "…ab")
|
||||
|
||||
let string7 = NSMutableAttributedString(string: "🐈⬛🐕🐄")
|
||||
string7.truncateHead(until: 6, offset: 1)
|
||||
XCTAssertEqual(string7.string, "…🐕🐄")
|
||||
}
|
||||
|
||||
|
||||
func testJoin() {
|
||||
|
||||
let attrs: [NSAttributedString] = [
|
||||
|
@ -9,7 +9,7 @@
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// © 2015-2022 1024jp
|
||||
// © 2015-2023 1024jp
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -311,26 +311,28 @@ final class StringExtensionsTests: XCTestCase {
|
||||
|
||||
func testComposedCharacterSequence() {
|
||||
|
||||
let blackDog = "🐕⬛️" as NSString // 5
|
||||
XCTAssertEqual(blackDog.boundaryOfComposedCharacterSequence(2, offsetBy: -1), 0)
|
||||
XCTAssertEqual(blackDog.boundaryOfComposedCharacterSequence(1, offsetBy: 1), blackDog.length)
|
||||
let blackDog = "🐕⬛" // 4
|
||||
XCTAssertEqual(blackDog.lowerBoundOfComposedCharacterSequence(2, offsetBy: 1), 0)
|
||||
|
||||
let string = "🐕🏴☠️🇯🇵🧑💻" as NSString // 2 5 4 5
|
||||
XCTAssertEqual(string.boundaryOfComposedCharacterSequence(9, offsetBy: -3), 0)
|
||||
XCTAssertEqual(string.boundaryOfComposedCharacterSequence(9, offsetBy: -2), 0)
|
||||
XCTAssertEqual(string.boundaryOfComposedCharacterSequence(9, offsetBy: -1), "🐕".utf16.count)
|
||||
XCTAssertEqual(string.boundaryOfComposedCharacterSequence(9, offsetBy: 0), "🐕🏴☠️".utf16.count)
|
||||
XCTAssertEqual(string.boundaryOfComposedCharacterSequence(9, offsetBy: 1), "🐕🏴☠️🇯🇵".utf16.count)
|
||||
XCTAssertEqual(string.boundaryOfComposedCharacterSequence(9, offsetBy: 2), "🐕🏴☠️🇯🇵🧑💻".utf16.count)
|
||||
XCTAssertEqual(string.boundaryOfComposedCharacterSequence(9, offsetBy: 3), "🐕🏴☠️🇯🇵🧑💻".utf16.count)
|
||||
let abcDog = "🐕⬛abc" // 4 1 1 1
|
||||
XCTAssertEqual(abcDog.lowerBoundOfComposedCharacterSequence(6, offsetBy: 1), "🐕⬛a".utf16.count)
|
||||
XCTAssertEqual(abcDog.lowerBoundOfComposedCharacterSequence(5, offsetBy: 1), "🐕⬛".utf16.count)
|
||||
|
||||
let abc = "abc" as NSString
|
||||
XCTAssertEqual(abc.boundaryOfComposedCharacterSequence(1, offsetBy: -2), 0)
|
||||
XCTAssertEqual(abc.boundaryOfComposedCharacterSequence(1, offsetBy: -1), 0)
|
||||
XCTAssertEqual(abc.boundaryOfComposedCharacterSequence(1, offsetBy: 0), 1)
|
||||
XCTAssertEqual(abc.boundaryOfComposedCharacterSequence(1, offsetBy: 1), 2)
|
||||
XCTAssertEqual(abc.boundaryOfComposedCharacterSequence(1, offsetBy: 2), 3)
|
||||
XCTAssertEqual(abc.boundaryOfComposedCharacterSequence(1, offsetBy: 3), 3)
|
||||
let dogDog = "🐕⬛🐕" // 4 2
|
||||
XCTAssertEqual(dogDog.lowerBoundOfComposedCharacterSequence(5, offsetBy: 1), 0)
|
||||
XCTAssertEqual(dogDog.lowerBoundOfComposedCharacterSequence(6, offsetBy: 1), "🐕⬛".utf16.count)
|
||||
XCTAssertEqual(dogDog.lowerBoundOfComposedCharacterSequence(6, offsetBy: 0), "🐕⬛🐕".utf16.count)
|
||||
|
||||
let string = "🐕🏴☠️🇯🇵🧑💻" // 2 5 4 5
|
||||
XCTAssertEqual(string.lowerBoundOfComposedCharacterSequence(9, offsetBy: 3), 0)
|
||||
XCTAssertEqual(string.lowerBoundOfComposedCharacterSequence(9, offsetBy: 2), 0)
|
||||
XCTAssertEqual(string.lowerBoundOfComposedCharacterSequence(9, offsetBy: 1), "🐕".utf16.count)
|
||||
XCTAssertEqual(string.lowerBoundOfComposedCharacterSequence(9, offsetBy: 0), "🐕🏴☠️".utf16.count)
|
||||
|
||||
let abc = "abc"
|
||||
XCTAssertEqual(abc.lowerBoundOfComposedCharacterSequence(1, offsetBy: 2), 0)
|
||||
XCTAssertEqual(abc.lowerBoundOfComposedCharacterSequence(1, offsetBy: 1), 0)
|
||||
XCTAssertEqual(abc.lowerBoundOfComposedCharacterSequence(1, offsetBy: 0), 1)
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user