From 495bf542cf59daa66697f13b53e692f818c17621 Mon Sep 17 00:00:00 2001 From: Matuush Date: Wed, 25 Jun 2025 00:01:49 +0200 Subject: [PATCH] IMPLEMENT TREAP PERSISTENCE --- editor.cpp | 23 ++-- everything.hpp | 68 ++++++++--- main.cpp | 8 +- selection.cpp | 39 ++++--- treap.cpp | 312 ++++++++++++++++++++++++++++++++++++++----------- 5 files changed, 347 insertions(+), 103 deletions(-) diff --git a/editor.cpp b/editor.cpp index ed6679a..d9a7be0 100644 --- a/editor.cpp +++ b/editor.cpp @@ -65,9 +65,6 @@ count_type Editor::get_number(string prompt) { } -// TODO undo - - // Jump to end of line void Editor::jump_line_end() { count_type line_size = file.get_line(file_offset+cur.r).size(); @@ -150,6 +147,12 @@ find_result Editor::find(string text) { } return {false, position()}; } +void Editor::replace(position p, count_type length, string text) { + string line_text = file.get_line(p.r); + line_text.erase(p.c, length); + line_text.insert(p.c, text); + file.update(p.r, line_text); +} bool Editor::take_action() { move(cur); @@ -190,6 +193,12 @@ bool Editor::take_action() { case 'w': save(); break; + case 'u': + if(file.version() > 0) { + jump({file.undo(), 0}); + print_file(file_offset); + } + break; case 'i': mode = INSERT; current_insert = file.get_line(file_offset + cur.r, false); @@ -223,7 +232,7 @@ bool Editor::take_action() { print_file(file_offset); break; case 'p': - selection.paste_selection(cur + file_offset); + file.compress_versions(selection.paste_selection(cur + file_offset)); print_file(file_offset); break; default: @@ -235,7 +244,7 @@ bool Editor::take_action() { switch(ch) { case ESC: mode = NORMAL; - file.set_line(cur.r, current_insert); + file.update(cur.r, current_insert); print_text(cur.r, substitute_tabs(current_insert)); current_insert = ""; break; @@ -251,7 +260,7 @@ bool Editor::take_action() { print_text(cur.r, substitute_tabs(current_insert)); break; case ENTER: - file.set_line(cur.r, current_insert); + file.update(cur.r, current_insert); file.split_line(cur + file_offset); cur.r++; cur.c = 0; move(cur); current_insert = file.get_line(cur.r); @@ -288,7 +297,7 @@ bool Editor::take_action() { mode = NORMAL; break; case 'x': - selection.remove_selection(cur + file_offset); + file.compress_versions(selection.remove_selection(cur + file_offset)+1); if(file_offset + cur.r >= file.size()) jump(cur + file_offset); print_file(file_offset); diff --git a/everything.hpp b/everything.hpp index 68a7059..d972e56 100644 --- a/everything.hpp +++ b/everything.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include @@ -9,6 +10,7 @@ constexpr int RANDOM_SEED = 666; using std::string; using std::vector; +using std::stack; typedef unsigned long long count_type; // Position @@ -29,43 +31,76 @@ struct line { count_type priority, size; // Treap innards string text; // Content line *left, *right; // Sons + line *next_in_version; + + void set_left(line *_left) { + left = _left; + update_size(); + } + void set_right(line *_right) { + right = _right; + update_size(); + } + + void update_size() { size = 1 + (left == nullptr ? 0 : left->size) + (right == nullptr ? 0 : right->size); } line(count_type _p, string _t) : - priority(_p), text(_t), size(1), left(nullptr), right(nullptr) {} + priority(_p), text(_t), size(1), left(nullptr), right(nullptr), next_in_version(nullptr) {} + + line(count_type _p, string _t, line *_left, line *_right, line *_next) : + priority(_p), text(_t), left(_left), right(_right), next_in_version(_next) { + update_size(); + } + + line(line *l) : + priority(l->priority), text(l->text), left(l->left), right(l->right), next_in_version(nullptr) { + update_size(); + } }; struct change { count_type row, count; }; // Treap data structure class Treap { - line *root; + stack changes; + stack root; + line* get_root() { return root.top(); }; - two_lines split(line *l, count_type k); - line* join(line *a, line *b); - line* join(two_lines two) { return join(two.first, two.second); } + line* insert(line *l, count_type k, string s, bool new_version); + line* remove(line *l, count_type k); + line* update(line *l, count_type k, string s, bool new_version); + line* pick_higher(line *l); + void delete_version(line *l); count_type get_size(line *l); + count_type get_priority(line *l); line* find(line *l, count_type k); - line* find(count_type k); count_type bulk_find(vector *vec, line *l, count_type k, count_type count); public: // General operations - Treap() { srand(120); root = nullptr; } - count_type size() { return get_size(root); } + Treap() { srand(RANDOM_SEED); root.push(nullptr); } + void construct(vector &line); + count_type size() { return get_size(get_root()); } void clear(); - // Line get operations - string get_line(count_type r, bool substitute_tab = 1); + // Versioning + count_type undo(); + count_type version() { return root.size()-1; } + void compress_versions(count_type count); + + // Line getters + string get_line(count_type r, bool substitute_tab = true); vector bulk_get_line(count_type r, count_type count); count_type get_tab_offset(position p) { return ::get_tab_offset(p.c, get_line(p.r, 0)); } - // Line set operations - void split_line(position p); - void insert(count_type k, string s); + // Line meta-operations + void insert(count_type k, string s, bool new_version = true); void remove(count_type k); - void set_line(count_type k, string s) { find(k)->text = s; } + void update(count_type k, string s, bool new_version = true); - // Substring operations + // Other line operations + void split_line(position p, bool should_compress = true); + void merge_line(count_type k, bool should_compress = true); void insert(position p, string t); void remove(position p, count_type len = 1); }; @@ -85,7 +120,8 @@ public: count_type size() { return file->size(); } void copy_selection(position p); - void paste_selection(position p); + count_type remove_selection(position p); + count_type paste_selection(position p); }; diff --git a/main.cpp b/main.cpp index eea38d2..eabaa6c 100644 --- a/main.cpp +++ b/main.cpp @@ -48,10 +48,14 @@ int main(int argc, const char *argv[]) { } // Load file - Treap file; string line; + vector lines; while (std::getline(infile, line)) - file.insert(file.size(), line); + lines.push_back(line); + + // Create a treap from file + Treap file; + file.construct(lines); // Init initscr(); diff --git a/selection.cpp b/selection.cpp index 551f680..84adcde 100644 --- a/selection.cpp +++ b/selection.cpp @@ -24,13 +24,6 @@ void Selection::apply_on_selection(position p, F func, G func2) { count_type size = ((start.r == end.r) ? end.c+1 : start_line.size()) - start.c; func2(start.r, start.c, size, file, &content); } -// Removing selection -void Selection::remove_selection(position p) { - apply_on_selection(p, - [](count_type r, Treap *file, Treap *sel){ file->remove(r); }, - [](count_type r, count_type start_c, count_type size, Treap *file, Treap *sel) { file->remove({r, start_c-file->get_tab_offset({r, start_c})}, size); } - ); -} // Copying selection void Selection::copy_selection(position p) { apply_on_selection(p, @@ -38,20 +31,40 @@ void Selection::copy_selection(position p) { [](count_type r, count_type start_c, count_type size, Treap *file, Treap *sel) { sel->insert(0, file->get_line(r, false).substr(start_c - file->get_tab_offset({r, start_c}), size)); } ); } +// Removing selection +count_type Selection::remove_selection(position p) { + apply_on_selection(p, + [](count_type r, Treap *file, Treap *sel){ file->remove(r); }, + [](count_type r, count_type start_c, count_type size, Treap *file, Treap *sel) { file->remove({r, start_c-file->get_tab_offset({r, start_c})}, size); } + ); + + count_type min = p.r, max = pos.r; + if(min > max) + min = pos.r, max = p.r; + + if(max > min) { + file->merge_line(min+1, false); + return max - min + 2; + } + else + return 1; +} // Pasting selection -void Selection::paste_selection(position p) { - if(content.size() == 0) return; +count_type Selection::paste_selection(position p) { + if(content.size() == 0) return 0; // Insert last line inside of this file->insert(p, content.get_line(content.size()-1, false)); - if(content.size() == 1) return; + if(content.size() == 1) return 1; // Insert first line in front and split - file->split_line(p); + file->split_line(p, false); file->insert(p, content.get_line(0, false)); - if(content.size() == 2) return; + if(content.size() == 2) return 4; // Insert lines for(count_type i = content.size()-2; i > 0; --i) - file->insert(p.r, content.get_line(i, false)); + file->insert(p.r+1, content.get_line(i, false)); + + return content.size()+2; } diff --git a/treap.cpp b/treap.cpp index 477740a..cbb4cd4 100644 --- a/treap.cpp +++ b/treap.cpp @@ -2,99 +2,258 @@ // Get size uf a subtreap count_type Treap::get_size(line *l) { - if(l == nullptr) return 0; + if(l == nullptr) + return 0; return l->size; } - -// Split treap by k-th element -two_lines Treap::split(line *l, count_type k) { - if(l == nullptr) return {nullptr, nullptr}; - - if(get_size(l->left) >= k) { - // In the left subtree - auto two = split(l->left, k); - l->left = two.second; - l->size -= get_size(two.first); - return {two.first, l}; - } - else { - // In the right subtree - auto two = split(l->right, k - (1+get_size(l->left))); - l->right = two.first; - l->size -= get_size(two.second); - return {l, two.second}; - } +count_type Treap::get_priority(line *l) { + if(l == nullptr) + return 0; + return l->priority; } -// Join two treaps -line* Treap::join(line *a, line *b) { - if(a == nullptr) return b; - if(b == nullptr) return a; +// Treap node rotations +line *rotate_left(line *l) { + line *r = l->right, *mid = r->left; - if(a->priority < b->priority) { - a->size += get_size(b); - a->right = join(a->right, b); - return a; + // Switch + l->set_right(mid); + r->set_left(l); + + return r; +} +line *rotate_right(line *r) { + line *l = r->left, *mid = l->right; + + // Switch + r->set_left(mid); + l->set_right(r); + + return l; +} + +// Meta-operations +line* Treap::insert(line *l, count_type k, string s, bool new_version) { + if(l == nullptr) + return new line(rand(), s); + + // Insert into left subtree + if(k <= get_size(l->left)) { + line *son = insert(l->left, k, s, new_version); + + if(new_version) + l = new line(l->priority, l->text, son, l->right, son); + else + l->set_left(son); + + if(get_priority(l->left) > get_priority(l)) + l = rotate_right(l); + } + + // Insert into right subtree + else { + line *son = insert(l->right, k - (get_size(l->left)+1), s, new_version); + + if(new_version) + l = new line(l->priority, l->text, l->left, son, son); + else + l->set_right(son); + + if(get_priority(l->right) > get_priority(l)) + l = rotate_left(l); + } + return l; +} +line* Treap::remove(line *l, count_type k) { + line *lson = l->left, *rson = l->right; + + // Remove from left subtree + if(k < get_size(lson)) { + line *son = remove(lson, k); + return new line(l->priority, l->text, son, rson, son); + } + // Remove this line + else if(k == get_size(l->left)) { + // Picking the son we can + if(lson == nullptr && rson == nullptr) + return nullptr; + else if(lson == nullptr) + return new line(l->priority, rson->text, rson->left, rson->right, nullptr); + else if(rson == nullptr) + return new line(l->priority, lson->text, lson->left, lson->right, nullptr); + + // Picking the son with higher priority + else if(get_priority(lson) >= get_priority(rson)) { + line *son = new line(lson); + line l_copy = line(l->priority, l->text, son, l->right, nullptr); + + rotate_right(&l_copy); + son->right = remove(&l_copy, get_size(l_copy.left)); + son->next_in_version = son->right; + son->update_size(); + + return son; + } + else { + line *son = new line(rson); + line l_copy = line(l->priority, l->text, l->left, son, nullptr); + + rotate_left(&l_copy); + son->left = remove(&l_copy, get_size(l_copy.left)); + son->next_in_version = son->left; + son->update_size(); + + return son; + } + } + // Remove from right subtree + else { + line *son = remove(rson, k - (get_size(lson)+1) ); + return new line(l->priority, l->text, lson, son, son); + } +} +line* Treap::update(line *l, count_type k, string s, bool new_version) { + if(new_version) { + if(k < get_size(l->left)) { + line *son = update(l->left, k, s, new_version); + return new line(l->priority, l->text, son, l->right, son); + } + else if(k == get_size(l->left)) + return new line(l->priority, s, l->left, l->right, nullptr); + else { + line *son = update(l->right, k - (1+get_size(l->left)), s, new_version); + return new line(l->priority, l->text, l->left, son, son); + } } else { - b->size += get_size(a); - b->left = join(a, b->left); - return b; + if(k < get_size(l->left)) + update(l->left, k, s, new_version); + else if(k == get_size(l->left)) + l->text = s; + else + update(l->right, k - (1+get_size(l->left)), s, new_version); + return nullptr; } } // Find k-th line of file line* Treap::find(line *l, count_type k) { - if(l == nullptr) return nullptr; + if(l == nullptr) + return nullptr; - if(k <= get_size(l->left)) + if(k < get_size(l->left)) return find(l->left, k); - else if(k == get_size(l->left)+1) + else if(k == get_size(l->left)) return l; else return find(l->right, k - (1+get_size(l->left)) ); } -line* Treap::find(count_type k) { - if(k >= root->size) - return nullptr; - // Don't find index k, but k-th line -> +1 - return find(root, k+1); + +void Treap::construct(vector &line) { + for(string text : line) + insert(size(), text, false); +} + +// Undo +void Treap::delete_version(line *l) { + if(l == nullptr) + return; + + line *next = l->next_in_version; + delete l; + delete_version(next); +} +count_type Treap::undo() { + if(changes.size() == 0) + return 0; + + // Get number of changes + change last = changes.top(); + changes.pop(); + + // Delete all treap versions within the change + for(count_type i = 0; i < last.count; i++) { + line *last_root = root.top(); + root.pop(); + delete_version(last_root); + } + return last.row; +} + +void Treap::compress_versions(count_type count) { + for(count_type i = 1; i < count; ++i) + changes.pop(); + if(count > 0) + changes.top().count = count; } // File access void Treap::clear() { - while(size() > 0) { - auto two = split(root, 1); - root = two.second; - delete two.first; - } + // Clear all versions + while(root.size() > 1) + undo(); + // Clear original file + while(size() > 0) + remove(0); } // Line insert -void Treap::insert(count_type k, string s) { - line *l = new line(rand(), s); - - if(root == nullptr) { - root = l; +void Treap::insert(count_type k, string s, bool new_version) { + if(k > size()) { + std::cerr << "Inserting out of file range\n"; return; } - auto two = split(root, k); - two.first = join(two.first, l); - root = join(two); + line* new_root = insert(get_root(), k, s, new_version); + + if(new_version) { + root.push(new_root); + changes.push({k, 1}); + } + else + root.top() = new_root; } // Line removal void Treap::remove(count_type k) { - auto two = split(root, k+1); - auto first_split = split(two.first, k); - delete first_split.second; - two.first = first_split.first; - root = join(two); + if(k >= size()) { + std::cerr << "Removing out of file range\n"; + return; + } + + if(size() == 1) + update(0, ""); + else { + root.push(remove(get_root(), k)); + changes.push({k, 1}); + } +} + +// Line text update +void Treap::update(count_type k, string s, bool new_version) { + if(k >= size()) { + std::cerr << "Updating out of file range\n"; + return; + } + + if(s == find(get_root(), k)->text) + return; + + line *new_root = update(get_root(), k, s, new_version); + + if(new_version) { + root.push(new_root); + changes.push({k, 1}); + } } // Accessing the file string Treap::get_line(count_type r, bool substitute_tab) { - string line = find(r)->text; + line *l = find(get_root(), r); + if(l == nullptr) { + std::cerr << r << " not found.\n"; + return ""; + } + string line = l->text; if(substitute_tab) return substitute_tabs(line); else @@ -129,20 +288,43 @@ count_type Treap::bulk_find(vector *vec, line *l, count_type k, count_ty // Access multiple adjacent vertices vector Treap::bulk_get_line(count_type r, count_type count) { vector ret(0); - bulk_find(&ret, root, r+1, count); + bulk_find(&ret, get_root(), r+1, count); return ret; } void Treap::insert(position p, string t) { - find(p.r)->text.insert(p.c-get_tab_offset(p), t); + string text = get_line(p.r, false); + text.insert(p.c-get_tab_offset(p), t); + if(text != get_line(p.r, false)) + update(p.r, text); } void Treap::remove(position p, count_type len) { - find(p.r)->text.erase(p.c-get_tab_offset(p),len); + string text = get_line(p.r, false); + text.erase(p.c-get_tab_offset(p),len); + if(text != get_line(p.r, false)) + update(p.r, text); } // Split line -void Treap::split_line(position p) { - string line = get_line(p.r); - string newline = line.substr(p.c, line.size()); - remove(p, line.size()-p.c); +void Treap::split_line(position p, bool should_compress) { + string line = get_line(p.r, false); + count_type place = p.c - get_tab_offset(p); + string newline = line.substr(place, line.size()); + + if(line.size()-place > 0) + remove(p, line.size()-place); insert(p.r+1, newline); + + if(should_compress) + compress_versions(2); +} +void Treap::merge_line(count_type k, bool should_compress) { + if(k == 0) + return; + + string text = get_line(k, false); + remove(k); + update(k-1, get_line(k-1, false) + text); + + if(should_compress) + compress_versions(2); }