1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! A sketch of a _selection_ API for collections that could live alongside _cursors_.
//!
//! The Rust standard library includes unstable cursor APIs for `LinkedList` ([RFC](https://github.com/rust-lang/rfcs/pull/2570), [tracking issue](https://github.com/rust-lang/rust/issues/58533)) and `BTreeMap`/`BTreeSet` ([API change proposal](https://github.com/rust-lang/libs-team/issues/141), [tracking issue](https://github.com/rust-lang/rust/issues/107540)).
//! There has been a lot of bikeshedding over the years about what exactly these interfaces should look like.
//! One point of contention has been whether a cursor should point to _elements_ in a collection, or _gaps_ between those elements.
//! The current APIs in `std` have gone in opposite directions here: `LinkedList` cursors point at elements, whereas `BTreeMap` cursors were [rewritten](https://github.com/rust-lang/rust/pull/118208) to point at gaps instead.
//!
//! I've personally leaned toward the "gaps" formulation if we can only have one.
//! At some point I also proposed having two separate cursor types and converting between them as you navigate, e.g., calling `next_gap(self)` on an `ElemCursor` would always successfully return a `GapCursor`.
//! But nobody liked that idea. :)
//!
//! More recently I've been thinking about how useful it could be to instead have a separate concept of collection _selections_, analogous to selections in a text editor.
//! A selection is:
//!
//! - Like a cursor in that you can move its start and end, and insert, modify, and delete elements around those endpoints.
//! - Like a range iterator in that it allows you to iterate over some subset of a collection.
//! - Like `extract_if` (formerly `drain_filter`), `split_off`, etc. in that it could allow efficiently removing or splitting off part(s) of a collection.
//!
//! A cursor can be turned into an empty selection, and a selection can be turned into a cursor at either of its endpoints (for a mutable selection you have to pick one, since it consumes the selection).
//!
//! I can imagine it being particularly useful for `BTreeMap`, but the sketch in this crate uses a fake (`Vec`-backed) linked list for simplicity, to focus on the API shape rather than implementation details.
//!
//! # Examples
//!
//! Moving a paragraph down past the next one, like a text editor's "move block down" command:
//!
//! ```
//! # use collection_selection_confection::LinkedList;
//! # let mut lines = LinkedList::new();
//! # let mut cursor = lines.cursor_mut_at_start();
//! # for line in ["Roses are red,", "violets are blue.", "", "Sugar is sweet,", "and so are you."] {
//! # cursor.insert_before(line.to_string());
//! # }
//! // lines:
//! // Roses are red,
//! // violets are blue.
//! //
//! // Sugar is sweet,
//! // and so are you.
//!
//! // Select the first paragraph and cut it, along with the blank line after it.
//! let mut selection = lines.cursor_mut_at_start().into_selection();
//! while selection.peek_after_end().is_some_and(|line| !line.is_empty()) {
//! selection.move_end_next();
//! }
//! let paragraph = selection.take();
//! selection.remove_after_end();
//!
//! // Move past the next paragraph and paste it there.
//! let mut cursor = selection.into_cursor_at_end();
//! while cursor.peek_after().is_some_and(|line| !line.is_empty()) {
//! cursor.move_next();
//! }
//! cursor.insert_before(String::new());
//! cursor.splice_before(paragraph);
//!
//! // lines:
//! // Sugar is sweet,
//! // and so are you.
//! //
//! // Roses are red,
//! // violets are blue.
//! # assert_eq!(
//! # lines.iter().map(String::as_str).collect::<Vec<_>>(),
//! # ["Sugar is sweet,", "and so are you.", "", "Roses are red,", "violets are blue."],
//! # );
//! ```
//!
//! Unwrapping hard-wrapped paragraphs into one line each, switching back and forth between a cursor and a selection:
//!
//! ```
//! # use collection_selection_confection::LinkedList;
//! # let mut lines = LinkedList::new();
//! # let mut cursor = lines.cursor_mut_at_start();
//! # for line in ["Roses are red,", "violets are blue.", "", "Sugar is sweet,", "and so are you."] {
//! # cursor.insert_before(line.to_string());
//! # }
//! // lines:
//! // Roses are red,
//! // violets are blue.
//! //
//! // Sugar is sweet,
//! // and so are you.
//!
//! let mut cursor = lines.cursor_mut_at_start();
//! loop {
//! // Select the next paragraph.
//! let mut selection = cursor.into_selection();
//! while selection.peek_after_end().is_some_and(|line| !line.is_empty()) {
//! selection.move_end_next();
//! }
//!
//! // Replace it with a single line.
//! let paragraph = selection.take();
//! if !paragraph.is_empty() {
//! let joined = paragraph.iter().map(String::as_str).collect::<Vec<_>>().join(" ");
//! selection.insert_at_start(joined);
//! }
//!
//! // Step over the blank line, or stop if we're at the end.
//! cursor = selection.into_cursor_at_end();
//! if !cursor.move_next() {
//! break;
//! }
//! }
//!
//! // lines:
//! // Roses are red, violets are blue.
//! //
//! // Sugar is sweet, and so are you.
//! # assert_eq!(
//! # lines.iter().map(String::as_str).collect::<Vec<_>>(),
//! # ["Roses are red, violets are blue.", "", "Sugar is sweet, and so are you."],
//! # );
//! ```
pub use ;