@@ -167,6 +167,11 @@ impl Rope {
167167 ( ) ,
168168 ) ;
169169
170+ if text. is_empty ( ) {
171+ self . check_invariants ( ) ;
172+ return ;
173+ }
174+
170175 #[ cfg( all( test, not( rust_analyzer) ) ) ]
171176 const NUM_CHUNKS : usize = 16 ;
172177 #[ cfg( not( all( test, not( rust_analyzer) ) ) ) ]
@@ -269,6 +274,23 @@ impl Rope {
269274 }
270275
271276 pub fn push_front ( & mut self , text : & str ) {
277+ if text. is_empty ( ) {
278+ return ;
279+ }
280+ if self . is_empty ( ) {
281+ self . push ( text) ;
282+ return ;
283+ }
284+ if self
285+ . chunks
286+ . first ( )
287+ . is_some_and ( |c| c. text . len ( ) + text. len ( ) <= chunk:: MAX_BASE )
288+ {
289+ self . chunks
290+ . update_first ( |first_chunk| first_chunk. prepend_str ( text) , ( ) ) ;
291+ self . check_invariants ( ) ;
292+ return ;
293+ }
272294 let suffix = mem:: replace ( self , Rope :: from ( text) ) ;
273295 self . append ( suffix) ;
274296 }
@@ -2339,6 +2361,119 @@ mod tests {
23392361 }
23402362 }
23412363
2364+ #[ test]
2365+ fn test_push_front_empty_text_on_empty_rope ( ) {
2366+ let mut rope = Rope :: new ( ) ;
2367+ rope. push_front ( "" ) ;
2368+ assert_eq ! ( rope. text( ) , "" ) ;
2369+ assert_eq ! ( rope. len( ) , 0 ) ;
2370+ }
2371+
2372+ #[ test]
2373+ fn test_push_front_empty_text_on_nonempty_rope ( ) {
2374+ let mut rope = Rope :: from ( "hello" ) ;
2375+ rope. push_front ( "" ) ;
2376+ assert_eq ! ( rope. text( ) , "hello" ) ;
2377+ }
2378+
2379+ #[ test]
2380+ fn test_push_front_on_empty_rope ( ) {
2381+ let mut rope = Rope :: new ( ) ;
2382+ rope. push_front ( "hello" ) ;
2383+ assert_eq ! ( rope. text( ) , "hello" ) ;
2384+ assert_eq ! ( rope. len( ) , 5 ) ;
2385+ assert_eq ! ( rope. max_point( ) , Point :: new( 0 , 5 ) ) ;
2386+ }
2387+
2388+ #[ test]
2389+ fn test_push_front_single_space ( ) {
2390+ let mut rope = Rope :: from ( "hint" ) ;
2391+ rope. push_front ( " " ) ;
2392+ assert_eq ! ( rope. text( ) , " hint" ) ;
2393+ assert_eq ! ( rope. len( ) , 5 ) ;
2394+ }
2395+
2396+ #[ gpui:: test( iterations = 50 ) ]
2397+ fn test_push_front_random ( mut rng : StdRng ) {
2398+ let initial_len = rng. random_range ( 0 ..=64 ) ;
2399+ let initial_text: String = RandomCharIter :: new ( & mut rng) . take ( initial_len) . collect ( ) ;
2400+ let mut rope = Rope :: from ( initial_text. as_str ( ) ) ;
2401+
2402+ let mut expected = initial_text;
2403+
2404+ for _ in 0 ..rng. random_range ( 1 ..=10 ) {
2405+ let prefix_len = rng. random_range ( 0 ..=32 ) ;
2406+ let prefix: String = RandomCharIter :: new ( & mut rng) . take ( prefix_len) . collect ( ) ;
2407+
2408+ rope. push_front ( & prefix) ;
2409+ expected. insert_str ( 0 , & prefix) ;
2410+
2411+ assert_eq ! (
2412+ rope. text( ) ,
2413+ expected,
2414+ "text mismatch after push_front({:?})" ,
2415+ prefix
2416+ ) ;
2417+ assert_eq ! ( rope. len( ) , expected. len( ) ) ;
2418+
2419+ let actual_summary = rope. summary ( ) ;
2420+ let expected_summary = TextSummary :: from ( expected. as_str ( ) ) ;
2421+ assert_eq ! (
2422+ actual_summary. len, expected_summary. len,
2423+ "len mismatch for {:?}" ,
2424+ expected
2425+ ) ;
2426+ assert_eq ! (
2427+ actual_summary. lines, expected_summary. lines,
2428+ "lines mismatch for {:?}" ,
2429+ expected
2430+ ) ;
2431+ assert_eq ! (
2432+ actual_summary. chars, expected_summary. chars,
2433+ "chars mismatch for {:?}" ,
2434+ expected
2435+ ) ;
2436+ assert_eq ! (
2437+ actual_summary. longest_row, expected_summary. longest_row,
2438+ "longest_row mismatch for {:?}" ,
2439+ expected
2440+ ) ;
2441+
2442+ // Verify offset-to-point and point-to-offset round-trip at boundaries.
2443+ for ( ix, _) in expected. char_indices ( ) . chain ( Some ( ( expected. len ( ) , '\0' ) ) ) {
2444+ assert_eq ! (
2445+ rope. point_to_offset( rope. offset_to_point( ix) ) ,
2446+ ix,
2447+ "offset round-trip failed at {} for {:?}" ,
2448+ ix,
2449+ expected
2450+ ) ;
2451+ }
2452+ }
2453+ }
2454+
2455+ #[ gpui:: test( iterations = 50 ) ]
2456+ fn test_push_front_large_prefix ( mut rng : StdRng ) {
2457+ let initial_len = rng. random_range ( 0 ..=32 ) ;
2458+ let initial_text: String = RandomCharIter :: new ( & mut rng) . take ( initial_len) . collect ( ) ;
2459+ let mut rope = Rope :: from ( initial_text. as_str ( ) ) ;
2460+
2461+ let prefix_len = rng. random_range ( 64 ..=256 ) ;
2462+ let prefix: String = RandomCharIter :: new ( & mut rng) . take ( prefix_len) . collect ( ) ;
2463+
2464+ rope. push_front ( & prefix) ;
2465+ let expected = format ! ( "{}{}" , prefix, initial_text) ;
2466+
2467+ assert_eq ! ( rope. text( ) , expected) ;
2468+ assert_eq ! ( rope. len( ) , expected. len( ) ) ;
2469+
2470+ let actual_summary = rope. summary ( ) ;
2471+ let expected_summary = TextSummary :: from ( expected. as_str ( ) ) ;
2472+ assert_eq ! ( actual_summary. len, expected_summary. len) ;
2473+ assert_eq ! ( actual_summary. lines, expected_summary. lines) ;
2474+ assert_eq ! ( actual_summary. chars, expected_summary. chars) ;
2475+ }
2476+
23422477 fn clip_offset ( text : & str , mut offset : usize , bias : Bias ) -> usize {
23432478 while !text. is_char_boundary ( offset) {
23442479 match bias {
0 commit comments