mirror of
https://github.com/notepad-plus-plus/notepad-plus-plus.git
synced 2025-10-28 10:04:01 +01:00
335 lines
10 KiB
C++
335 lines
10 KiB
C++
// Scintilla source code edit control
|
|
/** @file SplitVector.h
|
|
** Main data structure for holding arrays that handle insertions
|
|
** and deletions efficiently.
|
|
**/
|
|
// Copyright 1998-2007 by Neil Hodgson <neilh@scintilla.org>
|
|
// The License.txt file describes the conditions under which this software may be distributed.
|
|
|
|
#ifndef SPLITVECTOR_H
|
|
#define SPLITVECTOR_H
|
|
|
|
namespace Scintilla {
|
|
|
|
template <typename T>
|
|
class SplitVector {
|
|
protected:
|
|
std::vector<T> body;
|
|
T empty; /// Returned as the result of out-of-bounds access.
|
|
ptrdiff_t lengthBody;
|
|
ptrdiff_t part1Length;
|
|
ptrdiff_t gapLength; /// invariant: gapLength == body.size() - lengthBody
|
|
ptrdiff_t growSize;
|
|
|
|
/// Move the gap to a particular position so that insertion and
|
|
/// deletion at that point will not require much copying and
|
|
/// hence be fast.
|
|
void GapTo(ptrdiff_t position) noexcept {
|
|
if (position != part1Length) {
|
|
if (position < part1Length) {
|
|
// Moving the gap towards start so moving elements towards end
|
|
std::move_backward(
|
|
body.data() + position,
|
|
body.data() + part1Length,
|
|
body.data() + gapLength + part1Length);
|
|
} else { // position > part1Length
|
|
// Moving the gap towards end so moving elements towards start
|
|
std::move(
|
|
body.data() + part1Length + gapLength,
|
|
body.data() + gapLength + position,
|
|
body.data() + part1Length);
|
|
}
|
|
part1Length = position;
|
|
}
|
|
}
|
|
|
|
/// Check that there is room in the buffer for an insertion,
|
|
/// reallocating if more space needed.
|
|
void RoomFor(ptrdiff_t insertionLength) {
|
|
if (gapLength <= insertionLength) {
|
|
while (growSize < static_cast<ptrdiff_t>(body.size() / 6))
|
|
growSize *= 2;
|
|
ReAllocate(body.size() + insertionLength + growSize);
|
|
}
|
|
}
|
|
|
|
void Init() {
|
|
body.clear();
|
|
body.shrink_to_fit();
|
|
lengthBody = 0;
|
|
part1Length = 0;
|
|
gapLength = 0;
|
|
growSize = 8;
|
|
}
|
|
|
|
public:
|
|
/// Construct a split buffer.
|
|
SplitVector() : empty(), lengthBody(0), part1Length(0), gapLength(0), growSize(8) {
|
|
}
|
|
|
|
// Deleted so SplitVector objects can not be copied.
|
|
SplitVector(const SplitVector &) = delete;
|
|
SplitVector(SplitVector &&) = delete;
|
|
void operator=(const SplitVector &) = delete;
|
|
void operator=(SplitVector &&) = delete;
|
|
|
|
~SplitVector() {
|
|
}
|
|
|
|
ptrdiff_t GetGrowSize() const noexcept {
|
|
return growSize;
|
|
}
|
|
|
|
void SetGrowSize(ptrdiff_t growSize_) noexcept {
|
|
growSize = growSize_;
|
|
}
|
|
|
|
/// Reallocate the storage for the buffer to be newSize and
|
|
/// copy existing contents to the new buffer.
|
|
/// Must not be used to decrease the size of the buffer.
|
|
void ReAllocate(ptrdiff_t newSize) {
|
|
if (newSize < 0)
|
|
throw std::runtime_error("SplitVector::ReAllocate: negative size.");
|
|
|
|
if (newSize > static_cast<ptrdiff_t>(body.size())) {
|
|
// Move the gap to the end
|
|
GapTo(lengthBody);
|
|
gapLength += newSize - static_cast<ptrdiff_t>(body.size());
|
|
// RoomFor implements a growth strategy but so does vector::resize so
|
|
// ensure vector::resize allocates exactly the amount wanted by
|
|
// calling reserve first.
|
|
body.reserve(newSize);
|
|
body.resize(newSize);
|
|
}
|
|
}
|
|
|
|
/// Retrieve the element at a particular position.
|
|
/// Retrieving positions outside the range of the buffer returns empty or 0.
|
|
const T& ValueAt(ptrdiff_t position) const noexcept {
|
|
if (position < part1Length) {
|
|
if (position < 0) {
|
|
return empty;
|
|
} else {
|
|
return body[position];
|
|
}
|
|
} else {
|
|
if (position >= lengthBody) {
|
|
return empty;
|
|
} else {
|
|
return body[gapLength + position];
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Set the element at a particular position.
|
|
/// Setting positions outside the range of the buffer performs no assignment
|
|
/// but asserts in debug builds.
|
|
template <typename ParamType>
|
|
void SetValueAt(ptrdiff_t position, ParamType&& v) noexcept {
|
|
if (position < part1Length) {
|
|
PLATFORM_ASSERT(position >= 0);
|
|
if (position < 0) {
|
|
;
|
|
} else {
|
|
body[position] = std::forward<ParamType>(v);
|
|
}
|
|
} else {
|
|
PLATFORM_ASSERT(position < lengthBody);
|
|
if (position >= lengthBody) {
|
|
;
|
|
} else {
|
|
body[gapLength + position] = std::forward<ParamType>(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Retrieve the element at a particular position.
|
|
/// The position must be within bounds or an assertion is triggered.
|
|
const T &operator[](ptrdiff_t position) const noexcept {
|
|
PLATFORM_ASSERT(position >= 0 && position < lengthBody);
|
|
if (position < part1Length) {
|
|
return body[position];
|
|
} else {
|
|
return body[gapLength + position];
|
|
}
|
|
}
|
|
|
|
/// Retrieve reference to the element at a particular position.
|
|
/// This, instead of the const variant, can be used to mutate in-place.
|
|
/// The position must be within bounds or an assertion is triggered.
|
|
T &operator[](ptrdiff_t position) noexcept {
|
|
PLATFORM_ASSERT(position >= 0 && position < lengthBody);
|
|
if (position < part1Length) {
|
|
return body[position];
|
|
} else {
|
|
return body[gapLength + position];
|
|
}
|
|
}
|
|
|
|
/// Retrieve the length of the buffer.
|
|
ptrdiff_t Length() const noexcept {
|
|
return lengthBody;
|
|
}
|
|
|
|
/// Insert a single value into the buffer.
|
|
/// Inserting at positions outside the current range fails.
|
|
void Insert(ptrdiff_t position, T v) {
|
|
PLATFORM_ASSERT((position >= 0) && (position <= lengthBody));
|
|
if ((position < 0) || (position > lengthBody)) {
|
|
return;
|
|
}
|
|
RoomFor(1);
|
|
GapTo(position);
|
|
body[part1Length] = std::move(v);
|
|
lengthBody++;
|
|
part1Length++;
|
|
gapLength--;
|
|
}
|
|
|
|
/// Insert a number of elements into the buffer setting their value.
|
|
/// Inserting at positions outside the current range fails.
|
|
void InsertValue(ptrdiff_t position, ptrdiff_t insertLength, T v) {
|
|
PLATFORM_ASSERT((position >= 0) && (position <= lengthBody));
|
|
if (insertLength > 0) {
|
|
if ((position < 0) || (position > lengthBody)) {
|
|
return;
|
|
}
|
|
RoomFor(insertLength);
|
|
GapTo(position);
|
|
std::fill(body.data() + part1Length, body.data() + part1Length + insertLength, v);
|
|
lengthBody += insertLength;
|
|
part1Length += insertLength;
|
|
gapLength -= insertLength;
|
|
}
|
|
}
|
|
|
|
/// Add some new empty elements.
|
|
/// InsertValue is good for value objects but not for unique_ptr objects
|
|
/// since they can only be moved from once.
|
|
/// Callers can write to the returned pointer to transform inputs without copies.
|
|
T *InsertEmpty(ptrdiff_t position, ptrdiff_t insertLength) {
|
|
PLATFORM_ASSERT((position >= 0) && (position <= lengthBody));
|
|
if (insertLength > 0) {
|
|
if ((position < 0) || (position > lengthBody)) {
|
|
return nullptr;
|
|
}
|
|
RoomFor(insertLength);
|
|
GapTo(position);
|
|
for (ptrdiff_t elem = part1Length; elem < part1Length + insertLength; elem++) {
|
|
T emptyOne = {};
|
|
body[elem] = std::move(emptyOne);
|
|
}
|
|
lengthBody += insertLength;
|
|
part1Length += insertLength;
|
|
gapLength -= insertLength;
|
|
}
|
|
return body.data() + position;
|
|
}
|
|
|
|
/// Ensure at least length elements allocated,
|
|
/// appending zero valued elements if needed.
|
|
void EnsureLength(ptrdiff_t wantedLength) {
|
|
if (Length() < wantedLength) {
|
|
InsertEmpty(Length(), wantedLength - Length());
|
|
}
|
|
}
|
|
|
|
/// Insert text into the buffer from an array.
|
|
void InsertFromArray(ptrdiff_t positionToInsert, const T s[], ptrdiff_t positionFrom, ptrdiff_t insertLength) {
|
|
PLATFORM_ASSERT((positionToInsert >= 0) && (positionToInsert <= lengthBody));
|
|
if (insertLength > 0) {
|
|
if ((positionToInsert < 0) || (positionToInsert > lengthBody)) {
|
|
return;
|
|
}
|
|
RoomFor(insertLength);
|
|
GapTo(positionToInsert);
|
|
std::copy(s + positionFrom, s + positionFrom + insertLength, body.data() + part1Length);
|
|
lengthBody += insertLength;
|
|
part1Length += insertLength;
|
|
gapLength -= insertLength;
|
|
}
|
|
}
|
|
|
|
/// Delete one element from the buffer.
|
|
void Delete(ptrdiff_t position) {
|
|
PLATFORM_ASSERT((position >= 0) && (position < lengthBody));
|
|
DeleteRange(position, 1);
|
|
}
|
|
|
|
/// Delete a range from the buffer.
|
|
/// Deleting positions outside the current range fails.
|
|
/// Cannot be noexcept as vector::shrink_to_fit may be called and it may throw.
|
|
void DeleteRange(ptrdiff_t position, ptrdiff_t deleteLength) {
|
|
PLATFORM_ASSERT((position >= 0) && (position + deleteLength <= lengthBody));
|
|
if ((position < 0) || ((position + deleteLength) > lengthBody)) {
|
|
return;
|
|
}
|
|
if ((position == 0) && (deleteLength == lengthBody)) {
|
|
// Full deallocation returns storage and is faster
|
|
Init();
|
|
} else if (deleteLength > 0) {
|
|
GapTo(position);
|
|
lengthBody -= deleteLength;
|
|
gapLength += deleteLength;
|
|
}
|
|
}
|
|
|
|
/// Delete all the buffer contents.
|
|
void DeleteAll() {
|
|
DeleteRange(0, lengthBody);
|
|
}
|
|
|
|
/// Retrieve a range of elements into an array
|
|
void GetRange(T *buffer, ptrdiff_t position, ptrdiff_t retrieveLength) const noexcept {
|
|
// Split into up to 2 ranges, before and after the split then use memcpy on each.
|
|
ptrdiff_t range1Length = 0;
|
|
if (position < part1Length) {
|
|
const ptrdiff_t part1AfterPosition = part1Length - position;
|
|
range1Length = retrieveLength;
|
|
if (range1Length > part1AfterPosition)
|
|
range1Length = part1AfterPosition;
|
|
}
|
|
std::copy(body.data() + position, body.data() + position + range1Length, buffer);
|
|
buffer += range1Length;
|
|
position = position + range1Length + gapLength;
|
|
const ptrdiff_t range2Length = retrieveLength - range1Length;
|
|
std::copy(body.data() + position, body.data() + position + range2Length, buffer);
|
|
}
|
|
|
|
/// Compact the buffer and return a pointer to the first element.
|
|
/// Also ensures there is an empty element beyond logical end in case its
|
|
/// passed to a function expecting a NUL terminated string.
|
|
T *BufferPointer() {
|
|
RoomFor(1);
|
|
GapTo(lengthBody);
|
|
T emptyOne = {};
|
|
body[lengthBody] = std::move(emptyOne);
|
|
return body.data();
|
|
}
|
|
|
|
/// Return a pointer to a range of elements, first rearranging the buffer if
|
|
/// needed to make that range contiguous.
|
|
T *RangePointer(ptrdiff_t position, ptrdiff_t rangeLength) noexcept {
|
|
if (position < part1Length) {
|
|
if ((position + rangeLength) > part1Length) {
|
|
// Range overlaps gap, so move gap to start of range.
|
|
GapTo(position);
|
|
return body.data() + position + gapLength;
|
|
} else {
|
|
return body.data() + position;
|
|
}
|
|
} else {
|
|
return body.data() + position + gapLength;
|
|
}
|
|
}
|
|
|
|
/// Return the position of the gap within the buffer.
|
|
ptrdiff_t GapPosition() const noexcept {
|
|
return part1Length;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|