Skip to content

Commit

Permalink
Merge pull request #111 from norio-nomura/fix-index-of-byte-offset
Browse files Browse the repository at this point in the history
Fix indexOfByteOffset()
  • Loading branch information
jpsim committed Dec 4, 2015
2 parents 02484b9 + 17232f9 commit 9db1fdb
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 51 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ None.
when all characters matched character set.
[JP Simard](https://github.com/jpsim)

* Fix `indexOfByteOffset(offset:)` failing when string include some emoji.
[Norio Nomura](https://github.com/norio-nomura)
[#111](https://github.com/jpsim/SourceKitten/pull/111)

## 0.6.2

Expand Down
51 changes: 14 additions & 37 deletions Source/SourceKittenFramework/String+SourceKitten.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,6 @@ private let commentLinePrefixCharacterSet: NSCharacterSet = {
}()

extension NSString {
/**
Binary search for NSString index equivalent to byte offset.

- parameter offset: Byte offset.

- returns: NSString index, if any.
*/
private func indexOfByteOffset(offset: Int) -> Int? {
var usedLength = 0

var left = Int(floor(Double(offset)/2))
var right = min(length, offset + 1)
var midpoint = (left + right) / 2

for _ in left..<right {
getBytes(nil,
maxLength: Int.max,
usedLength: &usedLength,
encoding: NSUTF8StringEncoding,
options: [],
range: NSRange(location: 0, length: midpoint),
remainingRange: nil)
if usedLength < offset {
left = midpoint
midpoint = (right + left) / 2
} else if usedLength > offset {
right = midpoint
midpoint = (right + left) / 2
} else {
return midpoint
}
}
return nil
}

public func lineAndCharacterForCharacterOffset(offset: Int) -> (line: Int, character: Int)? {
let range = NSRange(location: offset, length: 0)
var numberOfLines = 0, index = 0, lineRangeStart = 0, previousIndex = 0
Expand Down Expand Up @@ -114,8 +79,9 @@ extension NSString {
- returns: An equivalent `NSRange`.
*/
public func byteRangeToNSRange(start start: Int, length: Int) -> NSRange? {
return indexOfByteOffset(start).flatMap { stringStart in
return indexOfByteOffset(start + length).map { stringEnd in
let string = self as String
return string.indexOfByteOffset(start).flatMap { stringStart in
return string.indexOfByteOffset(start + length).map { stringEnd in
return NSRange(location: stringStart, length: stringEnd - stringStart)
}
}
Expand Down Expand Up @@ -204,6 +170,17 @@ extension NSString {
}

extension String {
/**
UTF16 index equivalent to byte offset.

- parameter offset: Byte offset.

- returns: UTF16 index, if any.
*/
private func indexOfByteOffset(offset: Int) -> Int? {
return utf8.startIndex.advancedBy(offset).samePositionIn(utf16).map(utf16.startIndex.distanceTo)
}

/// Returns the `#pragma mark`s in the string.
/// Just the content; no leading dashes or leading `#pragma mark`.
public func pragmaMarks(filename: String, excludeRanges: [NSRange], limitRange: NSRange?) -> [SourceDeclaration] {
Expand Down
28 changes: 14 additions & 14 deletions Source/SourceKittenFrameworkTests/StringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,28 +124,28 @@ class StringTests: XCTestCase {
}

func testSubstringWithByteRange() {
let string = "😄123"
XCTAssertEqual(string.substringWithByteRange(start: 0, length: 4)!, "😄")
XCTAssertEqual(string.substringWithByteRange(start: 4, length: 1)!, "1")
let string = "👨‍👩‍👧‍👧123"
XCTAssertEqual(string.substringWithByteRange(start: 0, length: 25)!, "👨‍👩‍👧‍👧")
XCTAssertEqual(string.substringWithByteRange(start: 25, length: 1)!, "1")
}

func testSubstringLinesWithByteRange() {
let string = "😄\n123"
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 0)!, "😄\n")
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 4)!, "😄\n")
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 5)!, "😄\n")
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 6)!, string)
XCTAssertEqual(string.substringLinesWithByteRange(start: 6, length: 0)!, "123")
let string = "👨‍👩‍👧‍👧\n123"
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 0)!, "👨‍👩‍👧‍👧\n")
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 25)!, "👨‍👩‍👧‍👧\n")
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 26)!, "👨‍👩‍👧‍👧\n")
XCTAssertEqual(string.substringLinesWithByteRange(start: 0, length: 27)!, string)
XCTAssertEqual(string.substringLinesWithByteRange(start: 27, length: 0)!, "123")
}

func testLineRangeWithByteRange() {
XCTAssert("".lineRangeWithByteRange(start: 0, length: 0) == nil)
let string = "😄\n123"
let string = "👨‍👩‍👧‍👧\n123"
XCTAssert(string.lineRangeWithByteRange(start: 0, length: 0)! == (1, 1))
XCTAssert(string.lineRangeWithByteRange(start: 0, length: 4)! == (1, 1))
XCTAssert(string.lineRangeWithByteRange(start: 0, length: 5)! == (1, 2))
XCTAssert(string.lineRangeWithByteRange(start: 0, length: 6)! == (1, 2))
XCTAssert(string.lineRangeWithByteRange(start: 6, length: 0)! == (2, 2))
XCTAssert(string.lineRangeWithByteRange(start: 0, length: 25)! == (1, 1))
XCTAssert(string.lineRangeWithByteRange(start: 0, length: 26)! == (1, 2))
XCTAssert(string.lineRangeWithByteRange(start: 0, length: 27)! == (1, 2))
XCTAssert(string.lineRangeWithByteRange(start: 27, length: 0)! == (2, 2))
}
}

Expand Down

0 comments on commit 9db1fdb

Please sign in to comment.