MOTIVATION:

Swift strings support a few methods for inserting and replacing things in the 
middle of them:

mutating func insert(_ newElement: Character, at i: String.Index)

mutating func insert<S : Collection where S.Iterator.Element == 
Character>(contentsOf newElements: S, at i: String.Index)

mutating func replaceSubrange(_ bounds: ClosedRange<String.Index>, with 
newElements: String)

mutating func replaceSubrange(_ bounds: Range<String.Index>, with newElements: 
String)

mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == 
Character>(_: ClosedRange<String.Index>, with: C)

mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == 
Character>(_ bounds: Range<String.Index>, with newElements: C)

These work well, but sometimes is advantageous to know the range of the portion 
of the string taken up by the inserted elements—for example, if one is creating 
a UI element, one may want to highlight the inserted text. In the old days when 
strings were defined in terms of UTF-16 code units, this was easy enough—just 
make a range starting at the insertion point and with the length of the string 
you just inserted. In Swift it is awkward and verbose:

let insertedRange = 
replacementRange.lowerBound..<string.index(replacementRange.lowerBound, 
offsetBy: replacementString.distance(from: replacementString.startIndex, to: 
replacementString.endIndex))

PROPOSED SOLUTION:

Introduce a return value on the insert and replace methods that contains the 
range of the inserted characters or string:

@discardableResult mutating func insert(_ newElement: Character, at i: 
String.Index) -> Range<String.Index>

@discardableResult mutating func insert<S : Collection where S.Iterator.Element 
== Character>(contentsOf newElements: S, at i: String.Index) -> 
Range<String.Index>

@discardableResult mutating func replaceSubrange(_ bounds: 
ClosedRange<String.Index>, with newElements: String) -> Range<String.Index>

@discardableResult mutating func replaceSubrange(_ bounds: Range<String.Index>, 
with newElements: String) -> Range<String.Index>

@discardableResult mutating func replaceSubrange<C where C : Collection, 
C.Iterator.Element == Character>(_: ClosedRange<String.Index>, with: C) -> 
Range<String.Index>

@discardableResult mutating func replaceSubrange<C where C : Collection, 
C.Iterator.Element == Character>(_ bounds: Range<String.Index>, with 
newElements: C) -> Range<String.Index>

The return value would contain the range of the portion of the string now 
occupied by what was inserted.

If this change is considered acceptable, it could also be applied to the 
equivalent methods on RangeReplaceableCollection.

IMPACT ON EXISTING CODE:

The @discardableResult attribute would ensure that existing code would continue 
to work as is. The change in the signatures, however, would affect ABI 
compatibility.

If the change is made to the protocols, however, this would affect source 
compatibility, as concrete types conforming to the protocols would need to be 
updated.

ALTERNATIVES CONSIDERED:

If there are performance concerns with generating the ranges, String could 
simply have two versions of each method, one returning a range and one 
returning Void.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to