TextCursor

struct TextCursor

A 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
	}
}