@@ -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
+ . expect ( "Previous chunk must be present" ) ;
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 } ) ;
@@ -241,17 +265,21 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
241
265
///
242
266
/// Because the `chunk_identifier` can represent non-gap chunk, this method
243
267
/// returns a `Result`.
268
+ ///
269
+ /// This method returns a reference to the (first if many) newly created
270
+ /// `Chunk` that contains the `items`.
244
271
pub fn replace_gap_at < I > (
245
272
& mut self ,
246
273
items : I ,
247
274
chunk_identifier : ChunkIdentifier ,
248
- ) -> Result < ( ) , LinkedChunkError >
275
+ ) -> Result < & Chunk < Item , Gap , CAP > , LinkedChunkError >
249
276
where
250
277
I : IntoIterator < Item = Item > ,
251
278
I :: IntoIter : ExactSizeIterator ,
252
279
{
253
280
let chunk_identifier_generator = self . chunk_identifier_generator . clone ( ) ;
254
281
let chunk_ptr;
282
+ let new_chunk_ptr;
255
283
256
284
{
257
285
let chunk = self
@@ -269,8 +297,7 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
269
297
// Insert a new items chunk…
270
298
. insert_next ( Chunk :: new_items_leaked (
271
299
chunk_identifier_generator. generate_next ( ) . unwrap ( ) ,
272
- ) )
273
- // … and insert the items.
300
+ ) ) // … and insert the items.
274
301
. push_items ( items, & chunk_identifier_generator) ;
275
302
276
303
(
@@ -283,6 +310,11 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
283
310
}
284
311
} ;
285
312
313
+ new_chunk_ptr = chunk
314
+ . next
315
+ // SAFETY: A new `Chunk` has just been inserted, so it exists.
316
+ . unwrap ( ) ;
317
+
286
318
// Now that new items have been pushed, we can unlink the gap chunk.
287
319
chunk. unlink ( ) ;
288
320
@@ -301,11 +333,16 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
301
333
302
334
// Re-box the chunk, and let Rust does its job.
303
335
//
304
- // SAFETY: `chunk` is unlinked but it still exists in memory! We have its
305
- // pointer, which is valid and well aligned .
336
+ // SAFETY: `chunk` is unlinked and not borrowed anymore. `LinkedChunk` doesn't
337
+ // use it anymore, it's a leak. It is time to re-`Box` it and drop it .
306
338
let _chunk_boxed = unsafe { Box :: from_raw ( chunk_ptr. as_ptr ( ) ) } ;
307
339
308
- Ok ( ( ) )
340
+ Ok (
341
+ // SAFETY: `new_chunk_ptr` is valid, non-null and well-aligned. It's taken from
342
+ // `chunk`, and that's how the entire `LinkedChunk` type works. Pointer construction
343
+ // safety is guaranteed by `Chunk::new_items_leaked` and `Chunk::new_gap_leaked`.
344
+ unsafe { new_chunk_ptr. as_ref ( ) } ,
345
+ )
309
346
}
310
347
311
348
/// Get the chunk as a reference, from its identifier, if it exists.
@@ -334,23 +371,23 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
334
371
}
335
372
}
336
373
337
- /// Search for a chunk, and return its identifier.
374
+ /// Search backwards for a chunk, and return its identifier.
338
375
pub fn chunk_identifier < ' a , P > ( & ' a self , mut predicate : P ) -> Option < ChunkIdentifier >
339
376
where
340
377
P : FnMut ( & ' a Chunk < Item , Gap , CAP > ) -> bool ,
341
378
{
342
379
self . rchunks ( ) . find_map ( |chunk| predicate ( chunk) . then ( || chunk. identifier ( ) ) )
343
380
}
344
381
345
- /// Search for an item, and return its position.
382
+ /// Search backwards for an item, and return its position.
346
383
pub fn item_position < ' a , P > ( & ' a self , mut predicate : P ) -> Option < Position >
347
384
where
348
385
P : FnMut ( & ' a Item ) -> bool ,
349
386
{
350
387
self . ritems ( ) . find_map ( |( item_position, item) | predicate ( item) . then_some ( item_position) )
351
388
}
352
389
353
- /// Iterate over the chunks, backward .
390
+ /// Iterate over the chunks, backwards .
354
391
///
355
392
/// It iterates from the last to the first chunk.
356
393
pub fn rchunks ( & self ) -> LinkedChunkIterBackward < ' _ , Item , Gap , CAP > {
@@ -941,75 +978,60 @@ mod tests {
941
978
} ;
942
979
943
980
macro_rules! assert_items_eq {
944
- ( @_ [ $iterator: ident, $chunk_index : ident , $item_index : ident ] { [ -] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
981
+ ( @_ [ $iterator: ident ] { [ -] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
945
982
assert_items_eq!(
946
983
@_
947
- [ $iterator, $chunk_index , $item_index ]
984
+ [ $iterator ]
948
985
{ $( $rest ) * }
949
986
{
950
987
$( $accumulator ) *
951
- $chunk_index += 1 ;
988
+ {
989
+ let chunk = $iterator . next( ) . expect( "next chunk (expect gap)" ) ;
990
+ assert!( chunk. is_gap( ) , "chunk " ) ;
991
+ }
952
992
}
953
993
)
954
994
} ;
955
995
956
- ( @_ [ $iterator: ident, $chunk_index : ident , $item_index : ident ] { [ $( $item: expr ) ,* ] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
996
+ ( @_ [ $iterator: ident ] { [ $( $item: expr ) ,* ] $( $rest: tt ) * } { $( $accumulator: tt ) * } ) => {
957
997
assert_items_eq!(
958
998
@_
959
- [ $iterator, $chunk_index , $item_index ]
999
+ [ $iterator ]
960
1000
{ $( $rest ) * }
961
1001
{
962
1002
$( $accumulator ) *
963
- let _expected_chunk_identifier = $iterator . peek( ) . unwrap( ) . 1 . chunk_identifier( ) ;
964
- $(
965
- assert_matches!(
966
- $iterator . next( ) ,
967
- Some ( ( chunk_index, Position ( chunk_identifier, item_index) , & $item ) ) => {
968
- // Ensure the chunk index (from the enumeration) is correct.
969
- assert_eq!( chunk_index, $chunk_index) ;
970
- // Ensure the chunk identifier is the same for all items in this chunk.
971
- assert_eq!( chunk_identifier, _expected_chunk_identifier) ;
972
- // Ensure the item has the expected position.
973
- assert_eq!( item_index, $item_index) ;
974
- }
975
- ) ;
976
- $item_index += 1 ;
977
- ) *
978
- $item_index = 0 ;
979
- $chunk_index += 1 ;
1003
+ {
1004
+ let chunk = $iterator . next( ) . expect( "next chunk (expect items)" ) ;
1005
+ assert!( chunk. is_items( ) ) ;
1006
+
1007
+ let ChunkContent :: Items ( items) = chunk. content( ) else { unreachable!( ) } ;
1008
+
1009
+ let mut items_iterator = items. iter( ) ;
1010
+
1011
+ $(
1012
+ assert_eq!( items_iterator. next( ) , Some ( & $item ) ) ;
1013
+ ) *
1014
+
1015
+ assert!( items_iterator. next( ) . is_none( ) , "no more items" ) ;
1016
+ }
980
1017
}
981
1018
)
982
1019
} ;
983
1020
984
- ( @_ [ $iterator: ident, $chunk_index : ident , $item_index : ident ] { } { $( $accumulator: tt ) * } ) => {
1021
+ ( @_ [ $iterator: ident ] { } { $( $accumulator: tt ) * } ) => {
985
1022
{
986
- let mut $chunk_index = 0 ;
987
- let mut $item_index = 0 ;
988
1023
$( $accumulator ) *
1024
+ assert!( $iterator . next( ) . is_none( ) , "no more chunks" ) ;
989
1025
}
990
1026
} ;
991
1027
992
1028
( $linked_chunk: expr, $( $all: tt ) * ) => {
993
1029
assert_items_eq!(
994
1030
@_
995
- [ iterator, _chunk_index , _item_index ]
1031
+ [ iterator ]
996
1032
{ $( $all ) * }
997
1033
{
998
- let mut iterator = $linked_chunk
999
- . chunks( )
1000
- . enumerate( )
1001
- . filter_map( |( chunk_index, chunk) | match & chunk. content {
1002
- ChunkContent :: Gap ( ..) => None ,
1003
- ChunkContent :: Items ( items) => {
1004
- let identifier = chunk. identifier( ) ;
1005
-
1006
- Some ( items. iter( ) . enumerate( ) . map( move |( item_index, item) | {
1007
- ( chunk_index, Position ( identifier, item_index) , item)
1008
- } ) )
1009
- }
1010
- } )
1011
- . flatten( )
1012
- . peekable( ) ;
1034
+ let mut iterator = $linked_chunk. chunks( ) ;
1013
1035
}
1014
1036
)
1015
1037
}
@@ -1392,14 +1414,48 @@ mod tests {
1392
1414
assert_items_eq ! ( linked_chunk, [ 'a' ] [ -] [ 'b' , 'c' ] [ 'd' , 'e' , 'f' ] ) ;
1393
1415
}
1394
1416
1395
- // Insert at the beginning of a chunk.
1417
+ // Insert at the beginning of a chunk + it's the first chunk .
1396
1418
{
1397
1419
let position_of_a = linked_chunk. item_position ( |item| * item == 'a' ) . unwrap ( ) ;
1398
1420
linked_chunk. insert_gap_at ( ( ) , position_of_a) ?;
1399
1421
1422
+ // A new empty chunk is created as the first chunk.
1400
1423
assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ 'd' , 'e' , 'f' ] ) ;
1401
1424
}
1402
1425
1426
+ // Insert at the beginning of a chunk.
1427
+ {
1428
+ let position_of_d = linked_chunk. item_position ( |item| * item == 'd' ) . unwrap ( ) ;
1429
+ linked_chunk. insert_gap_at ( ( ) , position_of_d) ?;
1430
+
1431
+ // A new empty chunk is NOT created, i.e. `['d', 'e', 'f']` is not
1432
+ // split into `[]` + `['d', 'e', 'f']` because it's a waste of
1433
+ // space.
1434
+ assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ -] [ 'd' , 'e' , 'f' ] ) ;
1435
+ }
1436
+
1437
+ // Insert in an empty chunk + it's the first chunk.
1438
+ {
1439
+ let position_of_first_empty_chunk = Position ( ChunkIdentifier ( 0 ) , 0 ) ;
1440
+ assert_matches ! (
1441
+ linked_chunk. insert_gap_at( ( ) , position_of_first_empty_chunk) ,
1442
+ Err ( LinkedChunkError :: InvalidItemIndex { index: 0 } )
1443
+ ) ;
1444
+ }
1445
+
1446
+ // Insert in an empty chunk.
1447
+ {
1448
+ // Replace a gap by empty items.
1449
+ let gap_identifier = linked_chunk. chunk_identifier ( Chunk :: is_gap) . unwrap ( ) ;
1450
+ let position = linked_chunk. replace_gap_at ( [ ] , gap_identifier) ?. first_position ( ) ;
1451
+
1452
+ assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ ] [ 'd' , 'e' , 'f' ] ) ;
1453
+
1454
+ linked_chunk. insert_gap_at ( ( ) , position) ?;
1455
+
1456
+ assert_items_eq ! ( linked_chunk, [ ] [ -] [ 'a' ] [ -] [ 'b' , 'c' ] [ -] [ ] [ 'd' , 'e' , 'f' ] ) ;
1457
+ }
1458
+
1403
1459
// Insert in a chunk that does not exist.
1404
1460
{
1405
1461
assert_matches ! (
@@ -1445,7 +1501,9 @@ mod tests {
1445
1501
let gap_identifier = linked_chunk. chunk_identifier ( Chunk :: is_gap) . unwrap ( ) ;
1446
1502
assert_eq ! ( gap_identifier, ChunkIdentifier ( 1 ) ) ;
1447
1503
1448
- linked_chunk. replace_gap_at ( [ 'd' , 'e' , 'f' , 'g' , 'h' ] , gap_identifier) ?;
1504
+ let new_chunk =
1505
+ linked_chunk. replace_gap_at ( [ 'd' , 'e' , 'f' , 'g' , 'h' ] , gap_identifier) ?;
1506
+ assert_eq ! ( new_chunk. identifier( ) , ChunkIdentifier ( 3 ) ) ;
1449
1507
assert_items_eq ! (
1450
1508
linked_chunk,
1451
1509
[ 'a' , 'b' ] [ 'd' , 'e' , 'f' ] [ 'g' , 'h' ] [ 'l' , 'm' ]
@@ -1463,7 +1521,8 @@ mod tests {
1463
1521
let gap_identifier = linked_chunk. chunk_identifier ( Chunk :: is_gap) . unwrap ( ) ;
1464
1522
assert_eq ! ( gap_identifier, ChunkIdentifier ( 5 ) ) ;
1465
1523
1466
- linked_chunk. replace_gap_at ( [ 'w' , 'x' , 'y' , 'z' ] , gap_identifier) ?;
1524
+ let new_chunk = linked_chunk. replace_gap_at ( [ 'w' , 'x' , 'y' , 'z' ] , gap_identifier) ?;
1525
+ assert_eq ! ( new_chunk. identifier( ) , ChunkIdentifier ( 6 ) ) ;
1467
1526
assert_items_eq ! (
1468
1527
linked_chunk,
1469
1528
[ 'a' , 'b' ] [ 'd' , 'e' , 'f' ] [ 'g' , 'h' ] [ 'l' , 'm' ] [ 'w' , 'x' , 'y' ] [ 'z' ]
0 commit comments