struct TextCursorA grapheme-aware cursor over an immutable string or substring snapshot.
Marks are opaque StringIndex values tied to that snapshot. restore rejects marks from another source, while slices borrow the original storage. Advancing and predicate consumption operate on extended grapheme clusters.
Methods
func is_at_end() -> Bool
Returns whether the cursor is positioned at the past-the-end index.
View Source
pub func is_at_end() -> Bool {
self.position == self.source.end_index()
}func mark() -> StringIndex
Captures the current snapshot-bound position for later restore or slicing.
View Source
pub func mark() -> StringIndex {
self.position
}func peek() -> Character?
Borrows the grapheme cluster at the cursor without advancing.
View Source
pub func peek() -> Character? {
self.source.character(at: self.position)
}func remaining() -> Substring
Borrows the unconsumed suffix from the cursor through the end boundary.
View Source
pub func remaining() -> Substring {
self.source.slice(
StringRange(start: self.position, end: self.source.end_index())
)
}mut func advance() -> Character?
Yields the current grapheme cluster and advances to its next boundary.
At the end boundary this returns none and leaves the cursor unchanged.
View Source
pub mut func advance() -> Character? {
match self.peek() {
.some(character) -> {
if let .some(next) = self.source.index(after: self.position) {
self.position = next
}
.some(character)
},
.none -> .none
}
}mut func restore(to mark: StringIndex) -> Bool
Restores a previously captured position from this exact source snapshot.
A mark at the past-the-end index is valid. Marks from other strings, substrings, or bounds return false and leave the cursor unchanged.
View Source
pub mut func restore(to mark: StringIndex) -> Bool {
match self.source.character(at: mark) {
.some(_) -> {
self.position = mark
true
},
.none -> {
if mark == self.source.end_index() {
self.position = mark
true
} else {
false
}
}
}
}func slice(from mark: StringIndex) -> Substring
Borrows the half-open range from mark to the current cursor position.
mark must belong to this cursor's source snapshot and must not follow the current position; invalid ranges perform 'panic through string slicing.
View Source
pub func slice(from mark: StringIndex) -> Substring {
self.source.slice(StringRange(start: mark, end: self.position))
}mut func consume(while predicate: (Character) -> Bool) -> Substring
Advances while predicate accepts successive grapheme clusters.
The returned substring borrows the consumed range. The first rejected cluster remains unconsumed, and exhaustion returns the remaining suffix.
View Source
pub mut func consume(while predicate: (Character) -> Bool) -> Substring {
let start = self.position
loop {
match self.peek() {
.some(character) -> {
if predicate(character) == false {
break
}
self.advance()
},
.none -> {
break
}
}
}
self.source.slice(StringRange(start: start, end: self.position))
}mut func consume(text: String) -> Bool
Atomically consumes text when it matches at the current position.
Matching is byte-exact and grapheme-aligned through the remaining substring. On failure the cursor does not move.
View Source
pub mut func consume(text: String) -> Bool {
let remaining = self.remaining()
if remaining.starts_with_text(text) == false { return false }
match remaining.first_range(of: text) {
.some(range) -> {
let moved = self.source.index(
self.position,
offset_by: remaining.slice(range).count()
)
match moved {
.some(next) -> {
self.position = next
true
},
.none -> false
}
},
.none -> false
}
}