Compare commits

...

2 commits

Author SHA1 Message Date
IlushaShurupov
c5a1c8d111 Text editor Initial 2023-07-15 09:58:12 +03:00
IlushaShurupov
22c3db3bc0 Strings Initial 2023-07-14 22:09:28 +03:00
12 changed files with 1602 additions and 3 deletions

View file

@ -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")

View file

@ -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
View 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)

View 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;
}
}

View 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);

View 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

View 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
View 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);
}
}
};
}

66
Strings/tests/tests.cpp Normal file
View 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
View file

@ -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

554
TextEditor/TextEditor.cpp Normal file
View 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; }

382
TextEditor/TextEditor.hpp Normal file
View 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();
};
}