test "basic_usage" {
// Create an empty rope
let rope = Rope::new()
inspect(rope.rope_is_empty(), content="true")
// Create a rope from a string
let rope = Rope::from_string("Hello, World!")
inspect(rope.len_chars(), content="13")
inspect(rope.rope_to_string(), content="Hello, World!")
}test "text_info" {
let rope = Rope::from_string("Hello\nWorld\n!")
// Get various length measurements
inspect(rope.len_chars(), content="13") // Number of characters
inspect(rope.len_utf16_cu(), content="13") // Number of UTF-16 code units
inspect(rope.len_lines(), content="3") // Number of lines
}test "character_ops" {
let rope = Rope::from_string("Hello, 世界!")
// Get character at index (returns character code)
inspect(rope.rope_char_at(0), content="72") // 'H'
inspect(rope.rope_char_at(7), content="19990") // '世'
// Convert between character and UTF-16 indices
inspect(rope.char_to_utf16_cu(7), content="7")
inspect(rope.utf16_cu_to_char(7), content="7")
}test "modification" {
let rope = Rope::from_string("Hello World")
// Insert text at any position
let rope2 = rope.insert(5, ", Beautiful")
inspect(rope2.rope_to_string(), content="Hello, Beautiful World")
// Remove text ranges
let rope3 = rope2.remove(5, 17) // Remove ", Beautiful"
inspect(rope3.rope_to_string(), content="HelloWorld")
// Note: Original rope is unchanged (immutable)
inspect(rope.rope_to_string(), content="Hello World")
}test "line_ops" {
let rope = Rope::from_string("Line 1\nLine 2\nLine 3")
// Get line count
inspect(rope.len_lines(), content="3")
// Convert between character and line indices
inspect(rope.line_to_char(1), content="7") // Start of line 1
inspect(rope.char_to_line(7), content="1") // Character 7 is on line 1
// Get individual lines
inspect(rope.line(0), content="Line 1\n")
inspect(rope.line(1), content="Line 2\n")
inspect(rope.line(2), content="Line 3") // Last line without newline
}test "rope_ops" {
let rope1 = Rope::from_string("Hello")
let rope2 = Rope::from_string(" World")
// Append ropes
let combined = rope1.rope_append(rope2)
inspect(combined.rope_to_string(), content="Hello World")
// Split rope at position
let (left, right) = combined.split_at(5)
inspect(left.rope_to_string(), content="Hello")
inspect(right.rope_to_string(), content=" World")
// Create slices
let slice = combined.slice(0, 5)
inspect(slice.rope_to_string(), content="Hello")
}test "error_handling" {
let rope = Rope::from_string("Hello")
// Safe character access
match rope.try_char_at(0) {
Ok(char_code) => inspect(char_code, content="72") // 'H'
Err(err) => abort("Unexpected error")
}
// Out of bounds access returns error
match rope.try_char_at(10) {
Ok(_) => abort("Should have failed")
Err(err) => inspect(err.error_to_string(), content="Character index out of bounds: char index 10, Rope char length 5")
}
}test "unicode_handling" {
let rope = Rope::from_string("Hello, 世界! 🌍")
// All operations work with logical characters, not UTF-16 code units
inspect(rope.len_chars(), content="12")
// Character-based slicing works correctly with Unicode
let slice = rope.slice(7, 9) // Extract "世界"
inspect(slice.rope_to_string(), content="世界")
// Line operations handle Unicode correctly
let multiline = Rope::from_string("English\n中文\nEmoji🎉")
inspect(multiline.len_lines(), content="3")
inspect(multiline.line(1), content="中文\n")
}test "performance_tips" {
// Large text handling - rope structure scales well
let large_text = "This is a very long text that would be expensive to manipulate with regular strings...\n".repeat(1000)
let rope = Rope::from_string(large_text)
// Insertions and deletions are O(log N)
let modified = rope.insert(100, "INSERTED TEXT")
// Slicing is also O(log N) and shares data where possible
let slice = rope.slice(0, 1000)
// Multiple operations can be chained efficiently
let result = rope
.insert(50, "FIRST")
.insert(100, "SECOND")
.remove(200, 300)
.slice(0, 500)
inspect(result.len_chars() > 0, content="true")
}test "string_utils" {
// Character counting (handles surrogate pairs correctly)
inspect(count_chars("Hello 世界"), content="8")
// Line break counting (handles CRLF correctly)
inspect(count_line_breaks("Line1\r\nLine2\nLine3"), content="2")
// Character/UTF-16 index conversion
let text = "Hello"
inspect(char_to_utf16_cu_idx(text, 3), content="3")
inspect(utf16_cu_to_char_idx(text, 3), content="3")
// Character/line index conversion
let lines = "Line1\nLine2\nLine3"
inspect(char_to_line_idx(lines, 6), content="1")
inspect(line_to_char_idx(lines, 1), content="6")
}fn NodeChildren::children_insert(self : NodeChildren, index : Int, info : TextInfo, node : Node) -> Unitfn NodeChildren::find_child_at_line_break(self : NodeChildren, line_idx : UInt64) -> (Int, TextInfo)pub enum RopeError {
CharIndexOutOfBounds(Int, Int)
Utf16IndexOutOfBounds(Int, Int)
LineIndexOutOfBounds(Int, Int)
CharRangeInvalid(Int, Int)
CharRangeOutOfBounds(Int, Int, Int)
InvalidOperation(String)
}pub struct TextInfo {
utf16_cu : UInt64
chars : UInt64
line_breaks : UInt64
}fn char_at(text : String, char_idx : Int) -> Intfn char_to_line_idx(text : String, char_idx : Int) -> Intfn char_to_utf16_cu_idx(text : String, char_idx : Int) -> Intfn count_chars(text : String) -> Intfn count_line_breaks(text : String) -> Intfn ends_with_line_break(text : String) -> Boolfn last_line_start_char_idx(text : String) -> Intfn line_length_from_char(text : String, start_char_idx : Int) -> Intfn line_to_char_idx(text : String, line_idx : Int) -> Intfn utf16_cu_to_char_idx(text : String, utf16_idx : Int) -> Int