1 // Scintilla source code edit control
2 /** @file SplitVector.h
3 ** Main data structure for holding arrays that handle insertions
4 ** and deletions efficiently.
6 // Copyright 1998-2007 by Neil Hodgson <neilh@scintilla.org>
7 // The License.txt file describes the conditions under which this software may be distributed.
12 namespace Scintilla::Internal
{
18 T empty
; /// Returned as the result of out-of-bounds access.
20 ptrdiff_t part1Length
;
21 ptrdiff_t gapLength
; /// invariant: gapLength == body.size() - lengthBody
24 /// Move the gap to a particular position so that insertion and
25 /// deletion at that point will not require much copying and
27 void GapTo(ptrdiff_t position
) noexcept
{
28 if (position
!= part1Length
) {
30 if (gapLength
> 0) { // If gap to move
31 // This can never fail but std::move and std::move_backward are not noexcept.
32 if (position
< part1Length
) {
33 // Moving the gap towards start so moving elements towards end
35 body
.data() + position
,
36 body
.data() + part1Length
,
37 body
.data() + gapLength
+ part1Length
);
38 } else { // position > part1Length
39 // Moving the gap towards end so moving elements towards start
41 body
.data() + part1Length
+ gapLength
,
42 body
.data() + gapLength
+ position
,
43 body
.data() + part1Length
);
46 part1Length
= position
;
48 // Ignore any exception
53 /// Check that there is room in the buffer for an insertion,
54 /// reallocating if more space needed.
55 void RoomFor(ptrdiff_t insertionLength
) {
56 if (gapLength
< insertionLength
) {
57 while (growSize
< static_cast<ptrdiff_t>(body
.size() / 6))
59 ReAllocate(body
.size() + insertionLength
+ growSize
);
73 /// Construct a split buffer.
74 SplitVector() : empty(), lengthBody(0), part1Length(0), gapLength(0), growSize(8) {
77 // Deleted so SplitVector objects can not be copied.
78 SplitVector(const SplitVector
&) = delete;
79 SplitVector(SplitVector
&&) = delete;
80 void operator=(const SplitVector
&) = delete;
81 void operator=(SplitVector
&&) = delete;
86 ptrdiff_t GetGrowSize() const noexcept
{
90 void SetGrowSize(ptrdiff_t growSize_
) noexcept
{
94 /// Reallocate the storage for the buffer to be newSize and
95 /// copy existing contents to the new buffer.
96 /// Must not be used to decrease the size of the buffer.
97 void ReAllocate(ptrdiff_t newSize
) {
99 throw std::runtime_error("SplitVector::ReAllocate: negative size.");
101 if (newSize
> static_cast<ptrdiff_t>(body
.size())) {
102 // Move the gap to the end
104 gapLength
+= newSize
- static_cast<ptrdiff_t>(body
.size());
105 // RoomFor implements a growth strategy but so does vector::resize so
106 // ensure vector::resize allocates exactly the amount wanted by
107 // calling reserve first.
108 body
.reserve(newSize
);
109 body
.resize(newSize
);
113 /// Retrieve the element at a particular position.
114 /// Retrieving positions outside the range of the buffer returns empty or 0.
115 const T
& ValueAt(ptrdiff_t position
) const noexcept
{
116 if (position
< part1Length
) {
120 return body
[position
];
123 if (position
>= lengthBody
) {
126 return body
[gapLength
+ position
];
131 /// Set the element at a particular position.
132 /// Setting positions outside the range of the buffer performs no assignment
133 /// but asserts in debug builds.
134 template <typename ParamType
>
135 void SetValueAt(ptrdiff_t position
, ParamType
&& v
) noexcept
{
136 if (position
< part1Length
) {
137 PLATFORM_ASSERT(position
>= 0);
141 body
[position
] = std::forward
<ParamType
>(v
);
144 PLATFORM_ASSERT(position
< lengthBody
);
145 if (position
>= lengthBody
) {
148 body
[gapLength
+ position
] = std::forward
<ParamType
>(v
);
153 /// Retrieve the element at a particular position.
154 /// The position must be within bounds or an assertion is triggered.
155 const T
&operator[](ptrdiff_t position
) const noexcept
{
156 PLATFORM_ASSERT(position
>= 0 && position
< lengthBody
);
157 if (position
< part1Length
) {
158 return body
[position
];
160 return body
[gapLength
+ position
];
164 /// Retrieve reference to the element at a particular position.
165 /// This, instead of the const variant, can be used to mutate in-place.
166 /// The position must be within bounds or an assertion is triggered.
167 T
&operator[](ptrdiff_t position
) noexcept
{
168 PLATFORM_ASSERT(position
>= 0 && position
< lengthBody
);
169 if (position
< part1Length
) {
170 return body
[position
];
172 return body
[gapLength
+ position
];
176 /// Retrieve the length of the buffer.
177 ptrdiff_t Length() const noexcept
{
181 /// Insert a single value into the buffer.
182 /// Inserting at positions outside the current range fails.
183 void Insert(ptrdiff_t position
, T v
) {
184 PLATFORM_ASSERT((position
>= 0) && (position
<= lengthBody
));
185 if ((position
< 0) || (position
> lengthBody
)) {
190 body
[part1Length
] = std::move(v
);
196 /// Insert a number of elements into the buffer setting their value.
197 /// Inserting at positions outside the current range fails.
198 void InsertValue(ptrdiff_t position
, ptrdiff_t insertLength
, T v
) {
199 PLATFORM_ASSERT((position
>= 0) && (position
<= lengthBody
));
200 if (insertLength
> 0) {
201 if ((position
< 0) || (position
> lengthBody
)) {
204 RoomFor(insertLength
);
206 std::fill(body
.data() + part1Length
, body
.data() + part1Length
+ insertLength
, v
);
207 lengthBody
+= insertLength
;
208 part1Length
+= insertLength
;
209 gapLength
-= insertLength
;
213 /// Add some new empty elements.
214 /// InsertValue is good for value objects but not for unique_ptr objects
215 /// since they can only be moved from once.
216 /// Callers can write to the returned pointer to transform inputs without copies.
217 T
*InsertEmpty(ptrdiff_t position
, ptrdiff_t insertLength
) {
218 PLATFORM_ASSERT((position
>= 0) && (position
<= lengthBody
));
219 if (insertLength
> 0) {
220 if ((position
< 0) || (position
> lengthBody
)) {
223 RoomFor(insertLength
);
225 for (ptrdiff_t elem
= part1Length
; elem
< part1Length
+ insertLength
; elem
++) {
227 body
[elem
] = std::move(emptyOne
);
229 lengthBody
+= insertLength
;
230 part1Length
+= insertLength
;
231 gapLength
-= insertLength
;
233 return body
.data() + position
;
236 /// Ensure at least length elements allocated,
237 /// appending zero valued elements if needed.
238 void EnsureLength(ptrdiff_t wantedLength
) {
239 if (Length() < wantedLength
) {
240 InsertEmpty(Length(), wantedLength
- Length());
244 /// Insert text into the buffer from an array.
245 void InsertFromArray(ptrdiff_t positionToInsert
, const T s
[], ptrdiff_t positionFrom
, ptrdiff_t insertLength
) {
246 PLATFORM_ASSERT((positionToInsert
>= 0) && (positionToInsert
<= lengthBody
));
247 if (insertLength
> 0) {
248 if ((positionToInsert
< 0) || (positionToInsert
> lengthBody
)) {
251 RoomFor(insertLength
);
252 GapTo(positionToInsert
);
253 std::copy(s
+ positionFrom
, s
+ positionFrom
+ insertLength
, body
.data() + part1Length
);
254 lengthBody
+= insertLength
;
255 part1Length
+= insertLength
;
256 gapLength
-= insertLength
;
260 /// Delete one element from the buffer.
261 void Delete(ptrdiff_t position
) {
262 PLATFORM_ASSERT((position
>= 0) && (position
< lengthBody
));
263 DeleteRange(position
, 1);
266 /// Delete a range from the buffer.
267 /// Deleting positions outside the current range fails.
268 /// Cannot be noexcept as vector::shrink_to_fit may be called and it may throw.
269 void DeleteRange(ptrdiff_t position
, ptrdiff_t deleteLength
) {
270 PLATFORM_ASSERT((position
>= 0) && (position
+ deleteLength
<= lengthBody
));
271 if ((position
< 0) || ((position
+ deleteLength
) > lengthBody
)) {
274 if ((position
== 0) && (deleteLength
== lengthBody
)) {
275 // Full deallocation returns storage and is faster
277 } else if (deleteLength
> 0) {
279 lengthBody
-= deleteLength
;
280 gapLength
+= deleteLength
;
284 /// Delete all the buffer contents.
286 DeleteRange(0, lengthBody
);
289 /// Retrieve a range of elements into an array
290 void GetRange(T
*buffer
, ptrdiff_t position
, ptrdiff_t retrieveLength
) const {
291 // Split into up to 2 ranges, before and after the split then use memcpy on each.
292 ptrdiff_t range1Length
= 0;
293 if (position
< part1Length
) {
294 const ptrdiff_t part1AfterPosition
= part1Length
- position
;
295 range1Length
= retrieveLength
;
296 if (range1Length
> part1AfterPosition
)
297 range1Length
= part1AfterPosition
;
299 std::copy(body
.data() + position
, body
.data() + position
+ range1Length
, buffer
);
300 buffer
+= range1Length
;
301 position
= position
+ range1Length
+ gapLength
;
302 const ptrdiff_t range2Length
= retrieveLength
- range1Length
;
303 std::copy(body
.data() + position
, body
.data() + position
+ range2Length
, buffer
);
306 /// Compact the buffer and return a pointer to the first element.
307 /// Also ensures there is an empty element beyond logical end in case its
308 /// passed to a function expecting a NUL terminated string.
313 body
[lengthBody
] = std::move(emptyOne
);
317 /// Return a pointer to a range of elements, first rearranging the buffer if
318 /// needed to make that range contiguous.
319 T
*RangePointer(ptrdiff_t position
, ptrdiff_t rangeLength
) noexcept
{
320 if (position
< part1Length
) {
321 if ((position
+ rangeLength
) > part1Length
) {
322 // Range overlaps gap, so move gap to start of range.
324 return body
.data() + position
+ gapLength
;
326 return body
.data() + position
;
329 return body
.data() + position
+ gapLength
;
333 /// Return a pointer to a single element.
334 /// Does not rearrange the buffer.
335 const T
*ElementPointer(ptrdiff_t position
) const noexcept
{
336 if (position
< part1Length
) {
337 return body
.data() + position
;
339 return body
.data() + position
+ gapLength
;
343 /// Return the position of the gap within the buffer.
344 ptrdiff_t GapPosition() const noexcept
{