Strings Initial
This commit is contained in:
parent
9cf541a6ba
commit
eb9210de1c
12 changed files with 1602 additions and 3 deletions
|
|
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
project(Allocator)
|
project(Allocators)
|
||||||
|
|
||||||
### ---------------------- Static Library --------------------- ###
|
### ---------------------- Static Library --------------------- ###
|
||||||
file(GLOB SOURCES "./private/*.cpp")
|
file(GLOB SOURCES "./private/*.cpp")
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,4 @@ add_subdirectory(Utils)
|
||||||
add_subdirectory(Containers)
|
add_subdirectory(Containers)
|
||||||
add_subdirectory(Math)
|
add_subdirectory(Math)
|
||||||
add_subdirectory(Allocators)
|
add_subdirectory(Allocators)
|
||||||
|
add_subdirectory(Strings)
|
||||||
22
Strings/CMakeLists.txt
Normal file
22
Strings/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
project(Strings)
|
||||||
|
|
||||||
|
### ---------------------- Static Library --------------------- ###
|
||||||
|
file(GLOB SOURCES "./private/*.cpp")
|
||||||
|
file(GLOB HEADERS "./public/*.hpp")
|
||||||
|
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC Allocators Containers)
|
||||||
|
|
||||||
|
### -------------------------- Tests -------------------------- ###
|
||||||
|
enable_testing()
|
||||||
|
file(GLOB TEST_SOURCES "./tests/*.cpp")
|
||||||
|
add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES})
|
||||||
|
target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME} Utils)
|
||||||
|
add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests)
|
||||||
|
|
||||||
|
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib)
|
||||||
69
Strings/private/Logging.cpp
Normal file
69
Strings/private/Logging.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
|
||||||
|
#include "Logging.hpp"
|
||||||
|
#include "Allocators.hpp"
|
||||||
|
#include "TextEditor.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
Logger* gLogger = nullptr;
|
||||||
|
|
||||||
|
ualni Logger::Report::getLineCount() const {
|
||||||
|
return (ualni)(mLineOffsets.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String Logger::Report::getString() const {
|
||||||
|
return mData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::Report::calcLineCount() {
|
||||||
|
Input input = { mData.raw(), (ualni) mData.size() };
|
||||||
|
input.getLineOffsets(mLineOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::Report::Report() {
|
||||||
|
mData = " - ";
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::Report::Report(const String& text) : mData(text) {
|
||||||
|
calcLineCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::Report::Report(const String& text, Type type) : mType(type), mData(text) {
|
||||||
|
calcLineCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::write(const String& in, bool post, Report::Type type) {
|
||||||
|
StringTemplate copy = in;
|
||||||
|
copy.capture();
|
||||||
|
mBuff.pushBack(Report(copy, type));
|
||||||
|
|
||||||
|
mLineCount += mBuff.last()->data.getLineCount();
|
||||||
|
|
||||||
|
if (!mCursor) {
|
||||||
|
mCursor = mBuff.last();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post) {
|
||||||
|
printf("%s", in.raw());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String Logger::read() {
|
||||||
|
if (!mCursor) return {};
|
||||||
|
const Report& out = mCursor->data;
|
||||||
|
mCursor = mCursor->next;
|
||||||
|
return out.getString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::initializeGlobal() {
|
||||||
|
DEBUG_ASSERT(!gLogger)
|
||||||
|
gLogger = new Logger();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::deinitializeGlobal() {
|
||||||
|
DEBUG_ASSERT(gLogger)
|
||||||
|
delete gLogger;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Strings/private/Strings.cpp
Normal file
24
Strings/private/Strings.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
#include "Strings.hpp"
|
||||||
|
#include "Allocators.hpp"
|
||||||
|
#include "Logging.hpp"
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
bool initialize(const ModuleManifest*) {
|
||||||
|
Logger::initializeGlobal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deinitialize(const ModuleManifest*) {
|
||||||
|
Logger::deinitializeGlobal();
|
||||||
|
}
|
||||||
|
|
||||||
|
static tp::ModuleManifest* sModuleDependencies[] = {
|
||||||
|
&tp::gModuleContainers,
|
||||||
|
&tp::gModuleAllocators,
|
||||||
|
&tp::gModuleUtils,
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
ModuleManifest tp::gModuleStrings = ModuleManifest("Strings", initialize, deinitialize, sModuleDependencies);
|
||||||
554
Strings/private/TextEditor.cpp
Normal file
554
Strings/private/TextEditor.cpp
Normal file
|
|
@ -0,0 +1,554 @@
|
||||||
|
|
||||||
|
#include "TextEditor.hpp"
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
using namespace str;
|
||||||
|
|
||||||
|
bool tp::str::PiecesDS::Node::mInsertionOperation = false;
|
||||||
|
|
||||||
|
bool tp::str::isNewLineChar(Char character) {
|
||||||
|
return (character == '\n') || (character == '\r');
|
||||||
|
}
|
||||||
|
|
||||||
|
void Input::getLineOffsets(Buffer<Index>& out) {
|
||||||
|
halni lines = 0;
|
||||||
|
for (auto idx : Range(mLen)) {
|
||||||
|
if (isNewLineChar(mInput[idx])) {
|
||||||
|
lines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.reserve(lines + 2); // plust start and end offsets
|
||||||
|
lines = 0;
|
||||||
|
for (auto idx : Range(mLen)) {
|
||||||
|
if (isNewLineChar(mInput[idx])) {
|
||||||
|
out[lines + 1] = Index(idx + 1);
|
||||||
|
lines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out[0] = 0;
|
||||||
|
out[lines + 1] = mLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------- Pointer ---------------------- //
|
||||||
|
|
||||||
|
Pointer::Pointer() {
|
||||||
|
mCol = 0; mLine = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer::Pointer(LineId aLine, ColumnId aCol) {
|
||||||
|
mLine = aLine;
|
||||||
|
mCol = aCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer::Pointer(const Input& aInput) {
|
||||||
|
halni lines = 0;
|
||||||
|
halni col = 0;
|
||||||
|
for (auto idx : Range(aInput.mLen)) {
|
||||||
|
if (isNewLineChar(aInput.mInput[idx])) {
|
||||||
|
lines++;
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mLine = lines;
|
||||||
|
mCol = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pointer::operator+=(Char achar) {
|
||||||
|
if (isNewLineChar(achar)) {
|
||||||
|
mLine += 1;
|
||||||
|
mCol = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mCol += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pointer::operator+=(const Pointer& ap) {
|
||||||
|
*this = operator+(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer Pointer::operator-(const Pointer in) const {
|
||||||
|
if (mLine == in.mLine) {
|
||||||
|
return { 0, mCol - in.mCol };
|
||||||
|
}
|
||||||
|
return { mLine - in.mLine, mCol };
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer Pointer::operator+(const Pointer& ap) const {
|
||||||
|
if (ap.mLine == 0) {
|
||||||
|
return { mLine, mCol + ap.mCol };
|
||||||
|
}
|
||||||
|
return { mLine + ap.mLine, ap.mCol };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pointer::operator==(const Pointer& in) const {
|
||||||
|
return in.mCol == mCol && in.mLine == mLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pointer::operator>(const Pointer& in) const {
|
||||||
|
if (mLine > in.mLine) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (mLine < in.mLine) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCol > in.mCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pointer::operator>=(const Pointer& in) const {
|
||||||
|
return operator>(in) || in == *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer Piece::diffPtr() const {
|
||||||
|
return mEnd - mStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineId Piece::nLines() const {
|
||||||
|
return { mEnd.mLine - mStart.mLine };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------- Add Buff ---------------------- //
|
||||||
|
|
||||||
|
AddBuff::AddBuff() {
|
||||||
|
Input input = { "", 0 };
|
||||||
|
Buffer<Index> lineoffsets;
|
||||||
|
input.getLineOffsets(lineoffsets);
|
||||||
|
mLineOffsets.append(lineoffsets.mBuff(), (Index)lineoffsets.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddBuff::append(Input aInput) {
|
||||||
|
|
||||||
|
// TODO : errors here
|
||||||
|
|
||||||
|
Buffer<Index> lineoffsets;
|
||||||
|
aInput.getLineOffsets(lineoffsets);
|
||||||
|
|
||||||
|
if (lineoffsets.length() == 2) {
|
||||||
|
mLineOffsets[mLineOffsets.len() - 1] += lineoffsets[1] - lineoffsets[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
auto last_line_offset = mLineOffsets[mLineOffsets.len() - 1];
|
||||||
|
|
||||||
|
// offset last line ptr
|
||||||
|
mLineOffsets[mLineOffsets.len() - 1] += lineoffsets[1];
|
||||||
|
|
||||||
|
// offset all lines by mLineOffsets.mLast ptr
|
||||||
|
for (auto offset : lineoffsets) {
|
||||||
|
offset.data() += last_line_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLineOffsets.append(&lineoffsets[2], (Index)(lineoffsets.length() - 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
mBuff.append(aInput.mInput, aInput.mLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer AddBuff::endPtr() {
|
||||||
|
return { mLineOffsets.len() - 2, mLineOffsets[mLineOffsets.len() - 1] - mLineOffsets[mLineOffsets.len() - 2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------- Orig Buff ---------------------- //
|
||||||
|
|
||||||
|
OrigBuff::OrigBuff(Input aOriginal) {
|
||||||
|
mOriginal = aOriginal;
|
||||||
|
mOriginal.getLineOffsets(mLineOffsets);
|
||||||
|
|
||||||
|
mPtrStart = { 0, 0 };
|
||||||
|
mPtrEnd = mOriginal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OrigBuff::makeOwnOriginalInput() {
|
||||||
|
if (mOwnsInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto input_copy = new Char[mOriginal.mLen + 1];
|
||||||
|
DEBUG_ASSERT(sizeof(Char) == sizeof(uint1));
|
||||||
|
memcp(input_copy, mOriginal.mInput, mOriginal.mLen);
|
||||||
|
input_copy[mOriginal.mLen] = '\0';
|
||||||
|
mOriginal.mInput = input_copy;
|
||||||
|
mOwnsInput = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrigBuff::~OrigBuff() {
|
||||||
|
if (mOwnsInput) {
|
||||||
|
delete[] mOriginal.mInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------- Editor ---------------------- //
|
||||||
|
|
||||||
|
PieceCrs TextEditor::findPiece(Pointer ptr) const {
|
||||||
|
PieceCrs out;
|
||||||
|
if (!mPieces.nPieces()) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPieces.ptrSum() == ptr) {
|
||||||
|
out.piece = mPieces.maxNode(mPieces.head());
|
||||||
|
} else {
|
||||||
|
out.piece = mPieces.find(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_BREAK(!out.piece);
|
||||||
|
out.piece_ptr = out.piece->getFindKey();
|
||||||
|
out.ptr = ptr;
|
||||||
|
out.offset = out.ptr - out.piece_ptr;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditor::clampPtr(Pointer& ptr) const {
|
||||||
|
if (!mPieces.nPieces()) {
|
||||||
|
ptr = { 0, 0 };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLAMP(ptr.mLine, 0, mNLines());
|
||||||
|
|
||||||
|
auto crs = findPiece(Pointer{ ptr.mLine, 0 });
|
||||||
|
Index line_len = getPieceLineLen(crs.piece->mPiece, crs.offset.mLine);
|
||||||
|
|
||||||
|
auto next_piece_ptr = nextPiecePtr(crs.piece, crs.piece_ptr);
|
||||||
|
auto piece = crs.piece;
|
||||||
|
while (piece->mNext && ptr.mLine == next_piece_ptr.mLine) {
|
||||||
|
piece = piece->mNext;
|
||||||
|
next_piece_ptr = nextPiecePtr(piece, next_piece_ptr);
|
||||||
|
line_len += getPieceLineLen(piece->mPiece, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr.mLine != mNLines()) {
|
||||||
|
line_len -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLAMP(ptr.mCol, 0, line_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditor::setCursor(Pointer ptr) {
|
||||||
|
clampPtr(ptr);
|
||||||
|
mCursor = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditor::setCursorEnd(Pointer ptr) {
|
||||||
|
clampPtr(ptr);
|
||||||
|
mCursorEnd = ptr;
|
||||||
|
mCursor = mCursorEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Char* TextEditor::getPieceLine(const Piece* piece, LineId offset) const {
|
||||||
|
const Char* buff;
|
||||||
|
if (piece->mAddedBuff) {
|
||||||
|
if (offset == 0) {
|
||||||
|
buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mStart.mLine]] + piece->mStart.mCol;
|
||||||
|
}
|
||||||
|
else if (offset > piece->nLines()) {
|
||||||
|
buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mEnd.mLine]] + piece->mEnd.mCol;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mStart.mLine + offset]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (offset == 0) {
|
||||||
|
buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mStart.mLine]] + piece->mStart.mCol;
|
||||||
|
}
|
||||||
|
else if (offset > piece->nLines()) {
|
||||||
|
buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mEnd.mLine]] + piece->mEnd.mCol;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mStart.mLine + offset]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Char* TextEditor::getBuffLine(const Piece* piece, LineId offset) const {
|
||||||
|
const Char* buff;
|
||||||
|
if (piece->mAddedBuff) {
|
||||||
|
buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mStart.mLine + offset]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mStart.mLine + offset]];
|
||||||
|
}
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer TextEditor::prevPiecePtr(PiecesDS::Node* piece_node, Pointer aPtr) const {
|
||||||
|
if (!piece_node->mPrev) {
|
||||||
|
return { 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return piece_node->mPrev->getFindKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer TextEditor::nextPiecePtr(PiecesDS::Node* aPiece, Pointer aPtr) const {
|
||||||
|
if (!aPiece) return { -1, -1 };
|
||||||
|
|
||||||
|
if (aPiece->mNext) {
|
||||||
|
return aPiece->mNext->getFindKey();
|
||||||
|
}
|
||||||
|
return aPiece->getFindKey() + aPiece->mDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineId TextEditor::getPieceLineLen(Piece* aPiece, LineId line) const {
|
||||||
|
return LineId(getPieceLine(aPiece, line + 1) - getPieceLine(aPiece, line));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEditor::TextEditor(Input original) : mOrigBuff(original) {
|
||||||
|
DEBUG_ASSERT(original.mLen);
|
||||||
|
auto new_node = mPieces.insertAfter(0, { false, mOrigBuff.mPtrStart, mOrigBuff.mPtrEnd });
|
||||||
|
mPieces.pushUndoPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEditor::TextEditor() : mOrigBuff({ 0, 0 }) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Index TextEditor::mNLines() const {
|
||||||
|
return mPieces.ptrSum().mLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer TextEditor::getCursor() const {
|
||||||
|
return mCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer TextEditor::getCursorEnd() const {
|
||||||
|
return mCursorEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditor::insert(Input aInput) {
|
||||||
|
// handle selectiom
|
||||||
|
remove(true);
|
||||||
|
|
||||||
|
// save input to addbuff
|
||||||
|
auto start_ref = mAddBuff.endPtr();
|
||||||
|
mAddBuff.append(aInput);
|
||||||
|
auto end_ref = mAddBuff.endPtr();
|
||||||
|
auto input_diff = end_ref - start_ref;
|
||||||
|
|
||||||
|
PiecesDS::Node* prev_node = nullptr;
|
||||||
|
PiecesDS::Node* new_node = nullptr;
|
||||||
|
|
||||||
|
auto crs = findPiece(mCursor);
|
||||||
|
|
||||||
|
if (crs.piece && crs.piece->mPrev && crs.offset == Pointer{ 0, 0 }) {
|
||||||
|
crs.piece_ptr = prevPiecePtr(crs.piece, crs.piece_ptr);
|
||||||
|
crs.offset = crs.ptr - crs.piece_ptr;
|
||||||
|
crs.piece = crs.piece->mPrev;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crs.piece) {
|
||||||
|
if (crs.ptr == crs.piece_ptr) { // start
|
||||||
|
prev_node = crs.piece->mPrev;
|
||||||
|
}
|
||||||
|
else if (crs.offset == crs.piece->mPiece->diffPtr()) { // end
|
||||||
|
if (mLastInsertionCrs == crs.ptr && crs.piece->mPiece->mAddedBuff && crs.piece->mPiece->mEnd == start_ref) {
|
||||||
|
crs.piece->mPiece->mEnd += input_diff;
|
||||||
|
mPieces.update_cache(crs.piece);
|
||||||
|
goto END;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prev_node = crs.piece;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // middle
|
||||||
|
prev_node = splitPiece(crs.piece, crs.offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the new node
|
||||||
|
new_node = mPieces.insertAfter(prev_node, { true, start_ref, end_ref });
|
||||||
|
|
||||||
|
END:
|
||||||
|
mCursor = mCursor + input_diff;
|
||||||
|
mCursorEnd = mCursor;
|
||||||
|
mLastInsertionCrs = mCursor;
|
||||||
|
mPieces.pushUndoPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
PiecesDS::Node* TextEditor::splitPiece(PiecesDS::Node* piece, Pointer split_ptr) {
|
||||||
|
auto span = piece->mPiece->diffPtr();
|
||||||
|
|
||||||
|
if (span == split_ptr || split_ptr == Pointer{ 0, 0 }) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save temp data
|
||||||
|
auto end_ptr = piece->mPiece->mEnd;
|
||||||
|
auto left_piece = Piece{ piece->mPiece->mAddedBuff, piece->mPiece->mStart, piece->mPiece->mStart + split_ptr };
|
||||||
|
auto left_node = mPieces.insertAfter(piece->mPrev, left_piece);
|
||||||
|
|
||||||
|
// create right half
|
||||||
|
auto right_node = mPieces.insertAfter(left_node, { left_node->mPiece->mAddedBuff, left_node->mPiece->mEnd, end_ptr });
|
||||||
|
|
||||||
|
auto line_len = halni(getBuffLine(right_node->mPiece, 1) - getBuffLine(right_node->mPiece, 0));
|
||||||
|
if (line_len == right_node->mPiece->mStart.mCol) {
|
||||||
|
DBG_BREAK(1);
|
||||||
|
right_node->mPiece->mStart = { right_node->mPiece->mStart.mLine + 1, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove current node
|
||||||
|
mPieces.remove(right_node->mNext);
|
||||||
|
|
||||||
|
return left_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditor::remove(bool only_selection, Dir dir, DirUnit unit) {
|
||||||
|
if (!mPieces.nPieces()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCursor == mCursorEnd) {
|
||||||
|
if (only_selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir == LEFT) {
|
||||||
|
if (mCursor == Pointer(0, 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PieceCrs crs;
|
||||||
|
if (mCursor.mCol == nullptr) {
|
||||||
|
auto new_ptr = Pointer{ mCursor.mLine - 1, ENV_HALNI_MAX };
|
||||||
|
clampPtr(new_ptr);
|
||||||
|
crs = findPiece(new_ptr);
|
||||||
|
//crs = findPiece({ crs.ptr.mLine, crs.ptr.mCol - 1 });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
crs = findPiece({ mCursor.mLine, mCursor.mCol - 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
mCursorEnd = mCursor;
|
||||||
|
mCursor = crs.ptr;
|
||||||
|
}
|
||||||
|
else if (dir == RIGHT) {
|
||||||
|
auto crs = findPiece(Pointer{ mCursor.mLine, mCursor.mCol + 1 });
|
||||||
|
mCursorEnd = mCursor;
|
||||||
|
mCursor = crs.ptr;
|
||||||
|
|
||||||
|
if (mCursor == mCursorEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCursor > mCursorEnd) {
|
||||||
|
swap(mCursor, mCursorEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto crs = findPiece(mCursor);
|
||||||
|
|
||||||
|
// first and last pieces to be deleted
|
||||||
|
PiecesDS::Node* del_start = nullptr, *del_end = nullptr;
|
||||||
|
|
||||||
|
// find left
|
||||||
|
if (crs.offset == Pointer{ 0, 0 }) {
|
||||||
|
del_start = crs.piece;
|
||||||
|
}
|
||||||
|
else if (crs.piece->mPiece->diffPtr() == crs.offset) {
|
||||||
|
del_start = crs.piece->mNext;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
del_start = splitPiece(crs.piece, crs.offset)->mNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto crs_end = findPiece(mCursorEnd);
|
||||||
|
|
||||||
|
// find right
|
||||||
|
if (crs_end.offset == Pointer{ 0, 0 }) {
|
||||||
|
del_end = crs_end.piece->mPrev;
|
||||||
|
}
|
||||||
|
else if (crs_end.piece->mPiece->diffPtr() == crs_end.offset) {
|
||||||
|
del_end = crs_end.piece;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bool intersect = crs_end.piece == del_start;
|
||||||
|
del_end = splitPiece(crs_end.piece, crs_end.offset);
|
||||||
|
if (intersect) {
|
||||||
|
del_start = del_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_ASSERT(del_start && del_end);
|
||||||
|
auto end_piece = del_end->mNext ? del_end->mNext->mPiece : nullptr;
|
||||||
|
|
||||||
|
while (del_start && del_start->mPiece != end_piece) {
|
||||||
|
auto del = del_start;
|
||||||
|
|
||||||
|
// del_start will be reused by the avl-tree after deletion otherwise
|
||||||
|
// works as we go from left to right
|
||||||
|
if (!del_start->mNext || del_start->mHeight <= del_start->mNext->mHeight) {
|
||||||
|
del_start = del_start->mNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPieces.remove(del);
|
||||||
|
}
|
||||||
|
mCursorEnd = mCursor;
|
||||||
|
|
||||||
|
mPieces.pushUndoPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditor::undo() {
|
||||||
|
mPieces.undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditor::redo() {
|
||||||
|
mPieces.redo();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEditor::~TextEditor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ ITERATOR ------------------------------ //
|
||||||
|
|
||||||
|
Iterator TextEditor::begin() { return Iterator(this); }
|
||||||
|
alni TextEditor::end() { return nullptr; }
|
||||||
|
|
||||||
|
Iterator::Iterator(const TextEditor* aSelf) {
|
||||||
|
mSelf = aSelf;
|
||||||
|
mNode = mSelf->mPieces.first();
|
||||||
|
mVal = mSelf->getPieceLine(mNode->mPiece, 0)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Iterator::inputLeft() {
|
||||||
|
return mNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Char Iterator::character() {
|
||||||
|
return mVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Iterator::operator++() {
|
||||||
|
auto nl = isNewLineChar(mVal);
|
||||||
|
auto diff = Pointer{ nl, !nl };
|
||||||
|
mPtr += diff;
|
||||||
|
mOffset += diff;
|
||||||
|
|
||||||
|
if (mOffset == mNode->mPiece->diffPtr()) {
|
||||||
|
mNode = mNode->mNext;
|
||||||
|
mOffset = { 0, 0 };
|
||||||
|
if (!mNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mVal = mSelf->getPieceLine(mNode->mPiece, mOffset.mLine)[mOffset.mCol];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Iterator::operator!=(alni const& iter) {
|
||||||
|
return inputLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Iterator::operator==(alni const& iter) {
|
||||||
|
return !inputLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
Char Iterator::operator->() { return mVal; }
|
||||||
|
const Iterator& Iterator::operator*() { return *this; }
|
||||||
59
Strings/public/Logging.hpp
Normal file
59
Strings/public/Logging.hpp
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Buffer.hpp"
|
||||||
|
#include "List.hpp"
|
||||||
|
#include "Strings.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
|
||||||
|
class Report {
|
||||||
|
String mData;
|
||||||
|
Buffer<ualni> mLineOffsets;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Type { INFO, ERR, WARN, SUC } mType = INFO;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Report();
|
||||||
|
explicit Report(const String& text);
|
||||||
|
Report(const String& text, Type type);
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]] ualni getLineCount() const;
|
||||||
|
[[nodiscard]] String getString() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void calcLineCount();
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
List<Report>::Node* mCursor = nullptr;
|
||||||
|
List<Report> mBuff;
|
||||||
|
ualni mLineCount = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void write(const String& in, bool post = true, Report::Type type = Report::Type::INFO);
|
||||||
|
|
||||||
|
[[nodiscard]] String read();
|
||||||
|
[[nodiscard]] const List<Report>& getBuff() const { return mBuff; }
|
||||||
|
[[nodiscard]] ualni getLineCount() const { return mLineCount; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void initializeGlobal();
|
||||||
|
static void deinitializeGlobal();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Logger* gLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LOG(val) tp::str::gLogger->write((val), 1)
|
||||||
|
|
||||||
|
#ifdef ENV_BUILD_DEBUG
|
||||||
|
#define DEBUG_LOG(val) tp::str::gLogger->write((val), 1)
|
||||||
|
#else
|
||||||
|
#define DEBUG_LOG(val) (0)
|
||||||
|
#endif
|
||||||
174
Strings/public/StringLogic.hpp
Normal file
174
Strings/public/StringLogic.hpp
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
template<typename tChar = uint1>
|
||||||
|
class StringLogic {
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef halni Index;
|
||||||
|
typedef Index LineId;
|
||||||
|
typedef Index ColumnId;
|
||||||
|
|
||||||
|
static inline bool isNewLineChar(tChar in) { return (in == '\n') || (in == '\r'); }
|
||||||
|
static inline bool isEndChar(tChar in) { return in == '\0'; }
|
||||||
|
static constexpr tChar getEndChar() { return '\0'; }
|
||||||
|
|
||||||
|
static ualni calcLength(const tChar* in) {
|
||||||
|
const tChar* iter = in;
|
||||||
|
while (*iter != '\0') iter++;
|
||||||
|
return iter - in;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tChar* insertLogic(const tChar* cur, const tChar* tar, ualni at, ualni len) {
|
||||||
|
ualni curLen = calcLength(cur);
|
||||||
|
ualni allLen = curLen + len;
|
||||||
|
auto* out = new tChar[allLen + 1];
|
||||||
|
|
||||||
|
DEBUG_ASSERT(curLen >= 0)
|
||||||
|
DEBUG_ASSERT(len >= 0)
|
||||||
|
DEBUG_ASSERT(at < curLen + 1 && at >= 0)
|
||||||
|
|
||||||
|
for (ualni idx = 0; idx < at; idx++) {
|
||||||
|
out[idx] = cur[idx];
|
||||||
|
}
|
||||||
|
for (ualni idx = 0; idx < len; idx++) {
|
||||||
|
out[idx + at] = tar[idx];
|
||||||
|
}
|
||||||
|
for (ualni idx = at + len; idx < allLen; idx++) {
|
||||||
|
out[idx] = cur[idx - len];
|
||||||
|
}
|
||||||
|
out[allLen] = '\0';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tChar* removeLogic(const tChar* cur, ualni start, ualni end) {
|
||||||
|
ualni curLen = calcLength(cur);
|
||||||
|
ualni rangeLen = end - start;
|
||||||
|
ualni newLen = curLen - rangeLen;
|
||||||
|
auto* out = new tChar[newLen + 1];
|
||||||
|
out[newLen] = '\0';
|
||||||
|
for (ualni idx = 0; idx < start; idx++) {
|
||||||
|
out[idx] = cur[idx];
|
||||||
|
}
|
||||||
|
for (ualni idx = end; idx < curLen; idx++) {
|
||||||
|
out[idx - rangeLen] = cur[idx];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isEqualLogic(const tChar* left, const tChar* right) {
|
||||||
|
ualni idx = 0;
|
||||||
|
LOOP:
|
||||||
|
if (left[idx] == '\0' || right[idx] == '\0') {
|
||||||
|
if (left[idx] == '\0' && right[idx] == '\0') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (left[idx] != right[idx]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
goto LOOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ualni fromValueLength(alni val, ualni base) {
|
||||||
|
ualni iter = val;
|
||||||
|
ualni len = 0;
|
||||||
|
while (iter /= base) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
bool neg = val < 0;
|
||||||
|
len += 1 + ualni(neg);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tChar* stringFromValue(alni val, tChar* ownBuff, ualni base) {
|
||||||
|
tChar* out;
|
||||||
|
bool neg = val < 0;
|
||||||
|
ualni len = fromValueLength(val, base);
|
||||||
|
out = ownBuff ? ownBuff : new tChar[len + 1];
|
||||||
|
out[len] = '\0';
|
||||||
|
val = abs(val);
|
||||||
|
for (ualni i = len - 1; i >= int(neg); i--) {
|
||||||
|
out[i] = (tChar) (val % base + 48);
|
||||||
|
val /= (alni) base;
|
||||||
|
}
|
||||||
|
if (neg) {
|
||||||
|
out[0] = '-';
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ualni fromValueLength(alnf val, ualni base) {
|
||||||
|
auto rounded = (alni) val;
|
||||||
|
ualni mantissa = ( (alni) val - rounded) * 100000;
|
||||||
|
alni rounded_len = 0;
|
||||||
|
alni mantissa_len = 0;
|
||||||
|
while (rounded /= (alni) base) {
|
||||||
|
rounded_len++;
|
||||||
|
}
|
||||||
|
if (!rounded_len) {
|
||||||
|
rounded_len++;
|
||||||
|
}
|
||||||
|
while (mantissa /= base) {
|
||||||
|
mantissa_len++;
|
||||||
|
}
|
||||||
|
bool neg = val < 0;
|
||||||
|
bool dot = mantissa_len & 1;
|
||||||
|
alni tot_len = mantissa_len + rounded_len + dot + neg;
|
||||||
|
return tot_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tChar* fromValue(alnf, ualni, tChar*) {
|
||||||
|
DEBUG_BREAK(false)
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ualni fromValueLength(bool val) {
|
||||||
|
return val ? 4 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tChar* fromValue(bool val, tChar* ownBuff) {
|
||||||
|
alni len = val ? 4 : 5;
|
||||||
|
tChar* out = ownBuff ? ownBuff : new tChar[len + 1];
|
||||||
|
out[len] = '\0';
|
||||||
|
memCopy(out, val ? "True" : "False", len);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tChar* stringFromValue(int val, ualni base) {
|
||||||
|
return fromValue((alni) val, nullptr, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static tChar* fromValue(tChar val) {
|
||||||
|
auto* out = new tChar[2];
|
||||||
|
out[1] = '\0';
|
||||||
|
out[0] = val;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool toValue(const tChar*, alni&, ualni) {
|
||||||
|
DEBUG_BREAK(false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool toValue(const tChar*, alnf&, ualni) {
|
||||||
|
DEBUG_BREAK(false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool toValue(const tChar* in, bool& val) {
|
||||||
|
if (!calcLength(in)) return false;
|
||||||
|
if (isEqual(in, "False") || isEqual(in, "false") || isEqual(in, "0")) {
|
||||||
|
val = false;
|
||||||
|
} else {
|
||||||
|
val = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
248
Strings/public/Strings.hpp
Normal file
248
Strings/public/Strings.hpp
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PoolAllocator.hpp"
|
||||||
|
#include "StringLogic.hpp"
|
||||||
|
#include "TextEditor.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
// Static data
|
||||||
|
extern ModuleManifest gModuleStrings;
|
||||||
|
|
||||||
|
template<typename tChar>
|
||||||
|
class StringTemplate;
|
||||||
|
using String = StringTemplate<uint1>;
|
||||||
|
|
||||||
|
template<typename tChar = uint1>
|
||||||
|
class StringTemplate {
|
||||||
|
typedef StringLogic<tChar> StringLogic;
|
||||||
|
|
||||||
|
// Hidden slave class
|
||||||
|
class StringData {
|
||||||
|
|
||||||
|
// allows to edit each string with text editor inplace
|
||||||
|
class TextEditors {
|
||||||
|
enum { MAX_EDITED_STRINGS = 65536 }; // 2^16 - do not change mindlessly
|
||||||
|
typedef uint2 Address;
|
||||||
|
ChunkAlloc<TextEditor*, MAX_EDITED_STRINGS> mEditorsPool;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TextEditors() : mEditorsPool() {}
|
||||||
|
|
||||||
|
TextEditor* get(Address address) { return mEditorsPool.getBuff() + address; }
|
||||||
|
|
||||||
|
Address createTextEditor(const tChar* originalBuffer) {
|
||||||
|
auto entry = (TextEditor**) mEditorsPool.allocate(0);
|
||||||
|
*entry = new TextEditor( originalBuffer );
|
||||||
|
return Address(entry - mEditorsPool.getBuff());
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyTextEditor(Address address) {
|
||||||
|
auto entry = mEditorsPool.getBuff() + address;
|
||||||
|
mEditorsPool.deallocate(entry);
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static TextEditors sTextEditors;
|
||||||
|
|
||||||
|
public:
|
||||||
|
uint2 mIsConst; // source is non-modifiable
|
||||||
|
uint2 mEditedIdx; // editor id
|
||||||
|
uint4 mReferenceCount; // number of users of this
|
||||||
|
tChar* mBuff; // actual string data
|
||||||
|
|
||||||
|
public:
|
||||||
|
StringData() {
|
||||||
|
MODULE_SANITY_CHECK(gModuleStrings)
|
||||||
|
mBuff = (tChar*) "*";
|
||||||
|
mReferenceCount = 0;
|
||||||
|
mIsConst = true;
|
||||||
|
mEditedIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringData(const tChar* aBuff, bool aIsConst) {
|
||||||
|
MODULE_SANITY_CHECK(gModuleStrings)
|
||||||
|
DEBUG_ASSERT(aBuff)
|
||||||
|
mBuff = (tChar*) aBuff;
|
||||||
|
mReferenceCount = 0;
|
||||||
|
mIsConst = aIsConst;
|
||||||
|
mEditedIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit StringData(const tChar* aBuff) {
|
||||||
|
MODULE_SANITY_CHECK(gModuleStrings)
|
||||||
|
DEBUG_ASSERT(aBuff)
|
||||||
|
auto const len = StringLogic::calcLength(aBuff);
|
||||||
|
resize(len);
|
||||||
|
memCopy(mBuff, aBuff, len);
|
||||||
|
mReferenceCount = 0;
|
||||||
|
mIsConst = false;
|
||||||
|
mEditedIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] tChar* getBuffer() {
|
||||||
|
DEBUG_ASSERT(!isEditorMode())
|
||||||
|
if (isEditorMode()) return nullptr;
|
||||||
|
return mBuff;
|
||||||
|
}
|
||||||
|
|
||||||
|
~StringData() {
|
||||||
|
if (!mIsConst) delete[] mBuff;
|
||||||
|
if (isEditorMode()) sTextEditors.destroyTextEditor(mEditedIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void resize(ualni size) {
|
||||||
|
DEBUG_ASSERT(!isEditorMode() && mReferenceCount == 1 && !mIsConst)
|
||||||
|
delete[] mBuff;
|
||||||
|
mBuff = new tChar[size + 1];
|
||||||
|
mBuff[size] = StringLogic::getEndChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]] bool isEditorMode() const { return mEditedIdx; }
|
||||||
|
|
||||||
|
void createEditor() {
|
||||||
|
DEBUG_ASSERT(!isEditorMode())
|
||||||
|
if (isEditorMode()) mEditedIdx = sTextEditors.createTextEditor(mBuff);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] TextEditor* getEditor() const {
|
||||||
|
DEBUG_ASSERT(isEditorMode())
|
||||||
|
if (!isEditorMode()) return nullptr;
|
||||||
|
return sTextEditors.get(mEditedIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyEditor() {
|
||||||
|
DEBUG_ASSERT(isEditorMode() && mReferenceCount == 1 && !mIsConst)
|
||||||
|
if (!isEditorMode()) return;
|
||||||
|
auto editor = sTextEditors.get(mEditedIdx);
|
||||||
|
resize(editor.textSize());
|
||||||
|
for ( auto character : *editor) {
|
||||||
|
mBuff[character.idx] = character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyEditor() {
|
||||||
|
DEBUG_ASSERT(isEditorMode())
|
||||||
|
if (!isEditorMode()) return;
|
||||||
|
sTextEditors.destroyTextEditor(mEditedIdx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
class StringData* mStringData;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum { STRINGS_POOL_SIZE = 1024, };
|
||||||
|
static PoolAlloc<StringData, STRINGS_POOL_SIZE>* sStringPool;
|
||||||
|
static StringData sNullString;
|
||||||
|
|
||||||
|
// --------------------------------- Main Interface ---------------------------------//
|
||||||
|
public:
|
||||||
|
// Creates null string reference
|
||||||
|
StringTemplate() {
|
||||||
|
mStringData = &sNullString;
|
||||||
|
incReference(mStringData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// References existing string data
|
||||||
|
StringTemplate(const StringTemplate& in) {
|
||||||
|
if (in.isEditorMode()) {
|
||||||
|
// Copies raw data and claims ownership over it
|
||||||
|
mStringData = new (sStringPool->allocate(0)) StringData(in.mStringData->mBuff);
|
||||||
|
incReference(mStringData);
|
||||||
|
} else {
|
||||||
|
mStringData = in.mStringData;
|
||||||
|
incReference(mStringData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates new string data from raw data pointer.
|
||||||
|
// Does not own string buffer - string buffer will not be freed. Initializes as const memory.
|
||||||
|
explicit StringTemplate(const tChar* raw) {
|
||||||
|
mStringData = new (sStringPool->allocate(0)) StringData(raw, true);
|
||||||
|
incReference(mStringData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies raw data and claims ownership over it
|
||||||
|
explicit StringTemplate(tChar* raw) {
|
||||||
|
mStringData = new (sStringPool->allocate(0)) StringData(raw);
|
||||||
|
incReference(mStringData);
|
||||||
|
}
|
||||||
|
|
||||||
|
~StringTemplate() {
|
||||||
|
decReference(mStringData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
[[nodiscard]] bool isEditorMode() const {
|
||||||
|
return mStringData->isEditorMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void incReference(StringData* dp) {
|
||||||
|
dp->mReferenceCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decReference(StringData* dp) {
|
||||||
|
dp->mReferenceCount--;
|
||||||
|
if (!dp->mReferenceCount) {
|
||||||
|
sStringPool->deallocate(mStringData);
|
||||||
|
mStringData->~StringData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public: // Access
|
||||||
|
// used to read data
|
||||||
|
[[nodiscard]] const tChar* read() const {
|
||||||
|
return mStringData->mBuff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to write data
|
||||||
|
// output will be null if in TextEditing mode
|
||||||
|
[[nodiscard]] tChar* write() {
|
||||||
|
makeModifiable();
|
||||||
|
return mStringData->getBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// output will be null if in TextEditing mode
|
||||||
|
[[nodiscard]] tChar* resize() {
|
||||||
|
if (!mStringData->getEditor()) mStringData->resize();
|
||||||
|
return mStringData->getBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public: // TextEditor Mode
|
||||||
|
// Enters TextEditor Mode
|
||||||
|
void createEditor() { mStringData->createEdited(); }
|
||||||
|
|
||||||
|
// Access to TextEditor Context & Interface
|
||||||
|
[[nodiscard]] TextEditor* getEditor() { return mStringData->getEdited(); }
|
||||||
|
[[nodiscard]] const TextEditor* getEditor() const { return mStringData->getEdited(); }
|
||||||
|
|
||||||
|
// Applies TextEditor Mode To Current buffer
|
||||||
|
void applyEditor() { mStringData->applyEditor(); }
|
||||||
|
|
||||||
|
// Leaves TextEditor Mode
|
||||||
|
void destroyEditor() { mStringData->destroyEditor(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void makeModifiable() {
|
||||||
|
// We have no rights to modify if someone references that memory too
|
||||||
|
// So we need to create new copy of StringData
|
||||||
|
if (mStringData->mReferenceCount > 1) {
|
||||||
|
mStringData = new (sStringPool->allocate(0)) StringData(mStringData->mBuff);
|
||||||
|
incReference(mStringData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also if initial raw data was marked as const - create copy of raw data
|
||||||
|
// Note - raw data will be lost at this point if no other record of such is stored
|
||||||
|
if (mStringData->mIsConst) {
|
||||||
|
mStringData = new (sStringPool->allocate(0)) StringData(mStringData->mBuff);
|
||||||
|
incReference(mStringData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
382
Strings/public/TextEditor.hpp
Normal file
382
Strings/public/TextEditor.hpp
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Strings.hpp"
|
||||||
|
#include "Buffer.hpp"
|
||||||
|
#include "Tree.hpp"
|
||||||
|
|
||||||
|
namespace tp {
|
||||||
|
|
||||||
|
class PiecesDS {
|
||||||
|
|
||||||
|
struct Input {
|
||||||
|
const Char* mInput = nullptr;
|
||||||
|
Index mLen = 0;
|
||||||
|
void getLineOffsets(Buffer<Index>& out);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pointer {
|
||||||
|
LineId mLine;
|
||||||
|
ColumnId mCol;
|
||||||
|
|
||||||
|
Pointer();
|
||||||
|
Pointer(LineId aLine, ColumnId aCol);
|
||||||
|
Pointer(const Input& aInput);
|
||||||
|
void operator+=(Char achar);
|
||||||
|
void operator+=(const Pointer& ap);
|
||||||
|
Pointer operator-(const Pointer in) const;
|
||||||
|
Pointer operator+(const Pointer& ap) const;
|
||||||
|
bool operator==(const Pointer& in) const;
|
||||||
|
bool operator>(const Pointer& in) const;
|
||||||
|
bool operator>=(const Pointer& in) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Piece {
|
||||||
|
bool mAddedBuff = false;
|
||||||
|
Pointer mStart;
|
||||||
|
Pointer mEnd;
|
||||||
|
|
||||||
|
Pointer diffPtr() const;
|
||||||
|
LineId nLines() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Tree Node : Based on AvlNodeBlueprint
|
||||||
|
struct Node {
|
||||||
|
|
||||||
|
Node* mLeft = nullptr;
|
||||||
|
Node* mRight = nullptr;
|
||||||
|
Node* mParent = nullptr;
|
||||||
|
halni mHeight = 0;
|
||||||
|
|
||||||
|
Pointer mPtrLeft;
|
||||||
|
Pointer mPtrRight;
|
||||||
|
Pointer mDiff;
|
||||||
|
|
||||||
|
Node* mNext = nullptr;
|
||||||
|
Node* mPrev = nullptr;
|
||||||
|
Piece* mPiece = nullptr;
|
||||||
|
|
||||||
|
static bool mInsertionOperation;
|
||||||
|
|
||||||
|
Node() {}
|
||||||
|
Node(Pointer aKey) {
|
||||||
|
//mVal = aKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool disentRight(Pointer aKey) const {
|
||||||
|
return aKey > mPtrLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool disentLeft(Pointer aKey) const {
|
||||||
|
return mPtrLeft > aKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exactNode(Pointer aKey) const {
|
||||||
|
if (mInsertionOperation) {
|
||||||
|
return aKey > mPtrLeft && mPtrLeft + mDiff > aKey;
|
||||||
|
}
|
||||||
|
return aKey >= mPtrLeft && mPtrLeft + mDiff > aKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer getFindKey(Node* node = nullptr) const {
|
||||||
|
// node must be ancestor of this node
|
||||||
|
|
||||||
|
Pointer out = mPtrLeft;
|
||||||
|
|
||||||
|
if (node == this) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto iter = this;
|
||||||
|
while (iter->mParent != node) {
|
||||||
|
if (iter == iter->mParent->mRight) {
|
||||||
|
auto left_sub_tree = iter->mParent->mPtrLeft + iter->mParent->mDiff;
|
||||||
|
out = left_sub_tree + out;
|
||||||
|
}
|
||||||
|
iter = iter->mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
if (iter == iter->mParent->mRight) {
|
||||||
|
auto left_sub_tree = iter->mParent->mPtrLeft + iter->mParent->mDiff;
|
||||||
|
out = left_sub_tree + out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copyData(const Node& in) {
|
||||||
|
mPiece = in.mPiece;
|
||||||
|
mNext = in.mNext;
|
||||||
|
mPrev = in.mPrev;
|
||||||
|
|
||||||
|
if (mPrev) {
|
||||||
|
mPrev->mNext = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mNext) {
|
||||||
|
mNext->mPrev = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer keyInRightSubtree(Pointer key) const { return key - (mPtrLeft + mDiff); }
|
||||||
|
Pointer keyInLeftSubtree(Pointer key) const { return key; }
|
||||||
|
|
||||||
|
void updateTreeCahceCallBack() {
|
||||||
|
DEBUG_ASSERT(mPiece);
|
||||||
|
mDiff = mPiece->diffPtr();
|
||||||
|
if (mLeft) {
|
||||||
|
mPtrLeft = mLeft->sum();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mPtrLeft = { 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRight) {
|
||||||
|
mPtrRight = mRight->sum();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mPtrRight = { 0, 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer sum() const {
|
||||||
|
return (mPtrLeft + mDiff) + mPtrRight;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
AvlTree<Node, Pointer> mTree;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
PiecesDS() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* find(Pointer ptr) const {
|
||||||
|
return mTree.find(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* minNode(Node* node) const {
|
||||||
|
return mTree.minNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* maxNode(Node* node) const {
|
||||||
|
return mTree.maxNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* head() const {
|
||||||
|
return mTree.head();
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const Node& node, Pointer ptr) {
|
||||||
|
Node::mInsertionOperation = true;
|
||||||
|
mTree.insert(ptr, node);
|
||||||
|
Node::mInsertionOperation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* insertAfter(Node* node, Piece piece) {
|
||||||
|
auto key = node ? (node->getFindKey() + node->mDiff) : Pointer{ 0, 0 };
|
||||||
|
auto new_node = Node();
|
||||||
|
new_node.mPiece = new Piece(piece);
|
||||||
|
|
||||||
|
insert(new_node, key);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
auto err_node = mTree.isInvalid(mTree.head());
|
||||||
|
DBG_BREAK(err_node);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto out = mTree.find(key);
|
||||||
|
|
||||||
|
Node* next = out->mRight;
|
||||||
|
Node* prev = out->mLeft;
|
||||||
|
|
||||||
|
if (!(next || prev) && out->mParent) {
|
||||||
|
if (out->mParent->mLeft == out) {
|
||||||
|
next = out->mParent;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prev = out->mParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
out->mNext = next;
|
||||||
|
out->mPrev = next->mPrev;
|
||||||
|
next->mPrev = out;
|
||||||
|
if (out->mPrev) out->mPrev->mNext = out;
|
||||||
|
}
|
||||||
|
else if (prev) {
|
||||||
|
out->mPrev = prev;
|
||||||
|
out->mNext = prev->mNext;
|
||||||
|
prev->mNext = out;
|
||||||
|
if (out->mNext) out->mNext->mPrev = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(Node* aNode) {
|
||||||
|
auto key = aNode->getFindKey();
|
||||||
|
|
||||||
|
if (aNode->mPrev) {
|
||||||
|
aNode->mPrev->mNext = aNode->mNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aNode->mNext) {
|
||||||
|
aNode->mNext->mPrev = aNode->mPrev;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto del = aNode->mPiece;
|
||||||
|
mTree.remove(key);
|
||||||
|
delete del;
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
auto err_node = mTree.isInvalid(mTree.head());
|
||||||
|
DBG_BREAK(err_node);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* first() const {
|
||||||
|
return mTree.minNode(mTree.head());
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer ptrSum() const {
|
||||||
|
if (mTree.head()) {
|
||||||
|
return mTree.head()->sum();
|
||||||
|
}
|
||||||
|
return { 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
halni nPieces() const {
|
||||||
|
return mTree.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_cache(Node* node) {
|
||||||
|
if (node) {
|
||||||
|
node->updateTreeCahceCallBack();
|
||||||
|
update_cache(node->mParent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void undo() {}
|
||||||
|
void redo() {}
|
||||||
|
void pushUndoPoint() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AddBuff {
|
||||||
|
Buffer<Char> mBuff;
|
||||||
|
Buffer<Index> mLineOffsets;
|
||||||
|
Index mColumn = 0;
|
||||||
|
|
||||||
|
AddBuff();
|
||||||
|
void append(Input aInput);
|
||||||
|
Pointer endPtr();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OrigBuff {
|
||||||
|
bool mOwnsInput = false;
|
||||||
|
Input mOriginal;
|
||||||
|
Buffer<Index> mLineOffsets;
|
||||||
|
|
||||||
|
Pointer mPtrStart;
|
||||||
|
Pointer mPtrEnd;
|
||||||
|
|
||||||
|
// does not owns the original input by default
|
||||||
|
OrigBuff(Input aOriginal);
|
||||||
|
|
||||||
|
void makeOwnOriginalInput();
|
||||||
|
|
||||||
|
~OrigBuff();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PieceCrs {
|
||||||
|
Pointer piece_ptr;
|
||||||
|
Pointer offset;
|
||||||
|
Pointer ptr;
|
||||||
|
PiecesDS::Node* piece = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextEditor {
|
||||||
|
friend class Iterator;
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
friend TextEditor;
|
||||||
|
|
||||||
|
const TextEditor* mSelf;
|
||||||
|
PiecesDS::Node* mNode = nullptr;
|
||||||
|
Pointer mOffset = { 0, 0 };
|
||||||
|
Pointer mPtr = { 0, 0 };
|
||||||
|
Char mVal = 0;
|
||||||
|
|
||||||
|
Iterator(const TextEditor* mSelf);
|
||||||
|
bool inputLeft();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Char character();
|
||||||
|
void operator++();
|
||||||
|
bool operator!=(alni const& iter);
|
||||||
|
bool operator==(alni const& iter);
|
||||||
|
Char operator->();
|
||||||
|
const Iterator& operator*();
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// pieces of strings that construct the text
|
||||||
|
PiecesDS mPieces;
|
||||||
|
|
||||||
|
// string buffers to wich pieces can reffer
|
||||||
|
OrigBuff mOrigBuff;
|
||||||
|
AddBuff mAddBuff;
|
||||||
|
|
||||||
|
// active position on the text
|
||||||
|
Pointer mCursor;
|
||||||
|
Pointer mCursorEnd;
|
||||||
|
|
||||||
|
Pointer mLastInsertionCrs;
|
||||||
|
//private:
|
||||||
|
|
||||||
|
[[nodiscard]] PieceCrs findPiece(Pointer ptr) const;
|
||||||
|
[[nodiscard]] Pointer prevPiecePtr(PiecesDS::Node* piece_node, Pointer piece_pointer) const;
|
||||||
|
[[nodiscard]] Pointer nextPiecePtr(PiecesDS::Node* piece_node, Pointer piece_pointer) const;
|
||||||
|
|
||||||
|
[[nodiscard]] const Char* getBuffLine(const Piece*, LineId) const;
|
||||||
|
[[nodiscard]] const Char* getPieceLine(const Piece*, LineId) const;
|
||||||
|
[[nodiscard]] PiecesDS::Node* splitPiece(PiecesDS::Node* piece, Pointer split_ptr);
|
||||||
|
[[nodiscard]] LineId getPieceLineLen(Piece* aPiece, LineId line) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Dir : uint1 { LEFT, RIGHT, UP, DOWN };
|
||||||
|
enum DirUnit : uint1 { CHAR, WORD, TOK, LINE_END, LINE_START };
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
TextEditor();
|
||||||
|
explicit TextEditor(Input original);
|
||||||
|
|
||||||
|
[[nodiscard]] Index mNLines() const;
|
||||||
|
void clampPtr(Pointer& ptr) const;
|
||||||
|
[[nodiscard]] Pointer getCursor() const;
|
||||||
|
[[nodiscard]] Pointer getCursorEnd() const;
|
||||||
|
void setCursor(Pointer ptr);
|
||||||
|
void setCursorEnd(Pointer ptr);
|
||||||
|
|
||||||
|
void insert(Input aInput);
|
||||||
|
|
||||||
|
void remove(bool only_selection = false, Dir dir = LEFT, DirUnit unit = CHAR);
|
||||||
|
|
||||||
|
void undo();
|
||||||
|
void redo();
|
||||||
|
|
||||||
|
Iterator begin();
|
||||||
|
alni end();
|
||||||
|
|
||||||
|
~TextEditor();
|
||||||
|
};
|
||||||
|
}
|
||||||
66
Strings/tests/tests.cpp
Normal file
66
Strings/tests/tests.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include "allocators.h"
|
||||||
|
|
||||||
|
#include "strings.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "TextEditor.hpp"
|
||||||
|
|
||||||
|
#include "Logging.hpp"
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
void test_conversions() {
|
||||||
|
|
||||||
|
for (auto i : tp::Range(1000)) {
|
||||||
|
|
||||||
|
auto sign = bool(std::rand() % 2) ? 1 : -1;
|
||||||
|
|
||||||
|
auto floating = std::rand() / (tp::alnf)std::rand() * sign;
|
||||||
|
auto tmp = std::to_string(floating);
|
||||||
|
auto left = tp::string(tmp.c_str());
|
||||||
|
auto right = tp::string(floating);
|
||||||
|
DBG_BREAK(left != right);
|
||||||
|
auto back = (tp::alnf)right;
|
||||||
|
//DBG_BREAK(back != floating);
|
||||||
|
|
||||||
|
auto integer = std::rand() * sign;
|
||||||
|
tmp = std::to_string(integer);
|
||||||
|
left = tp::string(tmp.c_str());
|
||||||
|
right = tp::string(integer);
|
||||||
|
DBG_BREAK(left != right);
|
||||||
|
back = (tp::alni)right;
|
||||||
|
DBG_BREAK(back != integer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_editor() {
|
||||||
|
tp::string str = " initial ";
|
||||||
|
str.createEdited();
|
||||||
|
auto edited = str.getEdited();
|
||||||
|
edited->setCursor({0, 4});
|
||||||
|
edited->insert({"aaa", 3});
|
||||||
|
str.saveEdited();
|
||||||
|
str.clearEdited();
|
||||||
|
|
||||||
|
GLog->write(str, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
tp::alloc_init();
|
||||||
|
string::Initialize();
|
||||||
|
Logger::init();
|
||||||
|
//test_conversions();
|
||||||
|
test_editor();
|
||||||
|
Logger::deinit();
|
||||||
|
string::UnInitialize();
|
||||||
|
tp::alloc_uninit();
|
||||||
|
}
|
||||||
2
TODO
2
TODO
|
|
@ -7,7 +7,7 @@ Containers:
|
||||||
Buffer 2d !
|
Buffer 2d !
|
||||||
Add check copy times
|
Add check copy times
|
||||||
AVL dont copy data just swap
|
AVL dont copy data just swap
|
||||||
More tests on buff and avl
|
More tests on mBuff and avl
|
||||||
|
|
||||||
Strings:
|
Strings:
|
||||||
Implement
|
Implement
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue