@@ -197,6 +197,30 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
197
197
. chunk_mut ( chunk_identifier)
198
198
. ok_or ( LinkedChunkError :: InvalidChunkIdentifier { identifier : chunk_identifier } ) ?;
199
199
200
+ // If `item_index` is 0, we don't want to split the current items chunk to
201
+ // insert a new gap chunk, otherwise it would create an empty current items
202
+ // chunk. Let's handle this case in particular.
203
+ //
204
+ // Of course this optimisation applies if there is a previous chunk. Remember
205
+ // the invariant: a `Gap` cannot be the first chunk.
206
+ if item_index == 0 && chunk. is_items ( ) && chunk. previous . is_some ( ) {
207
+ let previous_chunk = chunk
208
+ . previous_mut ( )
209
+ // SAFETY: The `previous` chunk exists because we have tested
210
+ // `chunk.previous.is_some()` in the `if` statement.
211
+ . unwrap ( ) ;
212
+
213
+ previous_chunk. insert_next ( Chunk :: new_gap_leaked (
214
+ chunk_identifier_generator. generate_next ( ) . unwrap ( ) ,
215
+ content,
216
+ ) ) ;
217
+
218
+ // We don't need to update `self.last` because we have inserted a new chunk
219
+ // before `chunk`.
220
+
221
+ return Ok ( ( ) ) ;
222
+ }
223
+
200
224
let chunk = match & mut chunk. content {
201
225
ChunkContent :: Gap ( ..) => {
202
226
return Err ( LinkedChunkError :: ChunkIsAGap { identifier : chunk_identifier } ) ;
@@ -345,15 +369,15 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
345
369
}
346
370
}
347
371
348
- /// Search for a chunk, and return its identifier.
372
+ /// Search backward for a chunk, and return its identifier.
349
373
pub fn chunk_identifier < ' a , P > ( & ' a self , mut predicate : P ) -> Option < ChunkIdentifier >
350
374
where
351
375
P : FnMut ( & ' a Chunk < Item , Gap , CAP > ) -> bool ,
352
376
{
353
377
self . rchunks ( ) . find_map ( |chunk| predicate ( chunk) . then ( || chunk. identifier ( ) ) )
354
378
}
355
379
356
- /// Search for an item, and return its position.
380
+ /// Search backward for an item, and return its position.
357
381
pub fn item_position < ' a , P > ( & ' a self , mut predicate : P ) -> Option < Position >
358
382
where
359
383
P : FnMut ( & ' a Item ) -> bool ,
@@ -952,75 +976,60 @@ mod tests {
952
976
} ;
953
977
954
978
macro_rules! assert_items_eq {
955
- ( @_ [ $iterator: ident, $chunk_index : ident , $item_index : ident ] { [ -] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
979
+ ( @_ [ $iterator: ident ] { [ -] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
956
980
assert_items_eq!(
957
981
@_
958
- [ $iterator, $chunk_index , $item_index ]
982
+ [ $iterator ]
959
983
{ $( $rest ) * }
960
984
{
961
985
$( $accumulator ) *
962
- $chunk_index += 1 ;
986
+ {
987
+ let chunk = $iterator . next( ) . expect( "next chunk (expect gap)" ) ;
988
+ assert!( chunk. is_gap( ) , "chunk " ) ;
989
+ }
963
990
}
964
991
)
965
992
} ;
966
993
967
- ( @_ [ $iterator: ident, $chunk_index : ident , $item_index : ident ] { [ $( $item: expr ) ,* ] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
994
+ ( @_ [ $iterator: ident ] { [ $( $item: expr ) ,* ] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
968
995
assert_items_eq!(
969
996
@_
970
- [ $iterator, $chunk_index , $item_index ]
997
+ [ $iterator ]
971
998
{ $( $rest ) * }
972
999
{
973
1000
$( $accumulator ) *
974
- let _expected_chunk_identifier = $iterator . peek( ) . unwrap( ) . 1 . chunk_identifier( ) ;
975
- $(
976
- assert_matches!(
977
- $iterator . next( ) ,
978
- Some ( ( chunk_index, Position ( chunk_identifier, item_index) , & $item ) ) => {
979
- // Ensure the chunk index (from the enumeration) is correct.
980
- assert_eq!( chunk_index, $chunk_index) ;
981
- // Ensure the chunk identifier is the same for all items in this chunk.
982
- assert_eq!( chunk_identifier, _expected_chunk_identifier) ;
983
- // Ensure the item has the expected position.
984
- assert_eq!( item_index, $item_index) ;
985
- }
986
- ) ;
987
- $item_index += 1 ;
988
- ) *
989
- $item_index = 0 ;
990
- $chunk_index += 1 ;
1001
+ {
1002
+ let chunk = $iterator . next( ) . expect( "next chunk (expect items)" ) ;
1003
+ assert!( chunk. is_items( ) ) ;
1004
+
1005
+ let ChunkContent :: Items ( items) = chunk. content( ) else { unreachable!( ) } ;
1006
+
1007
+ let mut items_iterator = items. iter( ) ;
1008
+
1009
+ $(
1010
+ assert_eq!( items_iterator. next( ) , Some ( & $item ) ) ;
1011
+ ) *
1012
+
1013
+ assert!( items_iterator. next( ) . is_none( ) , "no more items" ) ;
1014
+ }
991
1015
}
992
1016
)
993
1017
} ;
994
1018
995
- ( @_ [ $iterator: ident, $chunk_index : ident , $item_index : ident ] { } { $( $accumulator: tt ) * } ) => {
1019
+ ( @_ [ $iterator: ident ] { } { $( $accumulator: tt ) * } ) => {
996
1020
{
997
- let mut $chunk_index = 0 ;
998
- let mut $item_index = 0 ;
999
1021
$( $accumulator ) *
1022
+ assert!( $iterator . next( ) . is_none( ) , "no more chunks" ) ;
1000
1023
}
1001
1024
} ;
1002
1025
1003
1026
( $linked_chunk: expr, $( $all: tt ) * ) => {
1004
1027
assert_items_eq!(
1005
1028
@_
1006
- [ iterator, _chunk_index , _item_index ]
1029
+ [ iterator ]
1007
1030
{ $( $all ) * }
1008
1031
{
1009
- let mut iterator = $linked_chunk
1010
- . chunks( )
1011
- . enumerate( )
1012
- . filter_map( |( chunk_index, chunk) | match & chunk. content {
1013
- ChunkContent :: Gap ( ..) => None ,
1014
- ChunkContent :: Items ( items) => {
1015
- let identifier = chunk. identifier( ) ;
1016
-
1017
- Some ( items. iter( ) . enumerate( ) . map( move |( item_index, item) | {
1018
- ( chunk_index, Position ( identifier, item_index) , item)
1019
- } ) )
1020
- }
1021
- } )
1022
- . flatten( )
1023
- . peekable( ) ;
1032
+ let mut iterator = $linked_chunk. chunks( ) ;
1024
1033
}
1025
1034
)
1026
1035
}
@@ -1403,14 +1412,48 @@ mod tests {
1403
1412
assert_items_eq ! ( linked_chunk, [ 'a' ] [ -] [ 'b' , 'c' ] [ 'd' , 'e' , 'f' ] ) ;
1404
1413
}
1405
1414
1406
- // Insert at the beginning of a chunk.
1415
+ // Insert at the beginning of a chunk + it's the first chunk .
1407
1416
{
1408
1417
let position_of_a = linked_chunk. item_position ( |item| * item == 'a' ) . unwrap ( ) ;
1409
1418
linked_chunk. insert_gap_at ( ( ) , position_of_a) ?;
1410
1419
1420
+ // A new empty chunk is created as the first chunk.
1411
1421
assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ 'd' , 'e' , 'f' ] ) ;
1412
1422
}
1413
1423
1424
+ // Insert at the beginning of a chunk.
1425
+ {
1426
+ let position_of_d = linked_chunk. item_position ( |item| * item == 'd' ) . unwrap ( ) ;
1427
+ linked_chunk. insert_gap_at ( ( ) , position_of_d) ?;
1428
+
1429
+ // A new empty chunk is NOT created, i.e. `['d', 'e', 'f']` is not
1430
+ // split into `[]` + `['d', 'e', 'f']` because it's a waste of
1431
+ // space.
1432
+ assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ -] [ 'd' , 'e' , 'f' ] ) ;
1433
+ }
1434
+
1435
+ // Insert in an empty chunk + it's the first chunk.
1436
+ {
1437
+ let position_of_first_empty_chunk = Position ( ChunkIdentifier ( 0 ) , 0 ) ;
1438
+ assert_matches ! (
1439
+ linked_chunk. insert_gap_at( ( ) , position_of_first_empty_chunk) ,
1440
+ Err ( LinkedChunkError :: InvalidItemIndex { index: 0 } )
1441
+ ) ;
1442
+ }
1443
+
1444
+ // Insert in an empty chunk.
1445
+ {
1446
+ // Replace a gap by empty items.
1447
+ let gap_identifier = linked_chunk. chunk_identifier ( Chunk :: is_gap) . unwrap ( ) ;
1448
+ let position = linked_chunk. replace_gap_at ( [ ] , gap_identifier) ?. first_position ( ) ;
1449
+
1450
+ assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ ] [ 'd' , 'e' , 'f' ] ) ;
1451
+
1452
+ linked_chunk. insert_gap_at ( ( ) , position) ?;
1453
+
1454
+ assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ -] [ ] [ 'd' , 'e' , 'f' ] ) ;
1455
+ }
1456
+
1414
1457
// Insert in a chunk that does not exist.
1415
1458
{
1416
1459
assert_matches ! (
0 commit comments