Compare commits

...

10 commits

8 changed files with 570 additions and 131 deletions

108
DOC.md Normal file
View file

@ -0,0 +1,108 @@
# How Ze works on the inside
## Terminal viewing
Viewing is done by remembering a file offset for the window.
For example a window viewing 60 lines on file offset 2 would show lines from 2 to 61.
Then we have a cursor, which just represents a row-position and a column-position.
With those two, we can interact with lines, where we have cursor, jump to certain lines and generally move the cursor.
## Overview
Ze represents the file as a *treap*.
A treap is a randomized search tree, which is a binary search tree for its keys and a max-heap for their *priorities*.
Internally, the treap operation do not based on node keys, but the sizes of their subtrees - this applies for the b.s.t. attributes.
Nodes of this treap contain lines from the file.
The treap is *semi-persistent*, which means it remembers all of its versions and can modify the last one.
On a higher level, the editor repeatedly takes user input and does corresponding actions.
Those actions are divided into three modes: *normal*, *insert* and *select*.
It uses `ncurses` c library for handling terminal input/output.
## Treap Semi-persistence
Ze uses a semi-persistence technique of remembering paths from root to a leaf.
The main idea is that whenever an editation step changes the treap, only nodes on such a path are changed.
This means that it is sufficient only to remember those paths.
With this method, the version of the treap is represented by the latest root node.
The root then remembers its son, which is on the change-path and so on.
Roll-back is done simply by following the latest path and erasing nodes it contains.
## Treap meta-operations
The core of all editation steps are three operations: *insert*, *remove* and *update*.
Every other operation can wrap something around these three.
Sometimes that includes using more of them - for that scenario the treap also remembers how many of these meta-operations were used.
Undo is then just erasing as many versions as the editation step used.
As an added bonus, it also remembers the line changed, which makes it possible to jump on the line, where the change occured.
### Insert
For insert, we need two modes - with and without version-change.
That is because of treap build - because of it we can just do as many inserts as there are lines of the loaded file and take it as an initial version.
The new-version-remembering mode works recursivelly - starting with root, it decides whether the left sons subtree has higher or the same size as is the desired index where a new line should be inserted.
If yes, then it calls itself on the left son, otherwise on the right son but with an index, that has the size of the left subtree + 1 subtracted from it.
When it is called on a non-existent node, it creates a new node and returns it.
With each return, a new node is created with the appropriate son pointer and it's remembered as the next node on its version path.
But that alone wouldn't suffice, because the treap also acts as a heap.
Here come node rotations in play.
If a returned node has higher priority than the original node, they are rotated to suit the heap rules.
At last, the treap remembers a new root, representing a new version.
The version-not-remembering mode works almost the same, but not creating new nodes, but instead modifying the treap.
### Remove
Remove doesn't need the option not to remember a new version, but it's a little more complicated.
If we're still chasing the node to remove, we simply follow the path to it, same as with insert.
But when we find the node, then comes the fun.
If any one of the removed nodes sons are non-existent, the removed node is replaced by one that exists, if there is one.
But if both sons are real, we pick the one with higher priority to replace the removed node.
We do this with a handy rotation, which keeps b.s.t. rules, but gets the higher-priority son higher.
Then, when we have the desired son where we want him, we call the remove recursively on the node, we put lower.
Internally, this is done by making a permanent copy of the son and a temporary copy of the removed node, so we can rotate them and not affect the previous versions.
Each return then creates a new node, just as with an insert.
### Update
With the knowledge of insert and delete, update is pretty simple.
We find the desired point to update by following sizes of subtrees just as with the previous two operations.
Then, when we find it, we create a new, identical node with the new text and return that.
Then with each return, we create our trail of breadcrumbs.
## Other editation actions
Everything else, which changes the treap in any way, can be done by using these three meta-operations.
### Select mode and selection
Operations on selected text are generalized by a function called `apply_on_selection()`.
It just takes a function to apply to the lines selected in the file and applies it on them.
It can remove the selected text from the file - that is done with treap removes and updates on the edges.
It can copy the selected text into the selectoin.
The selection is just a vector of lines.
We can paste them into the file afterwards.
### Insert mode
One write on one line should create only one version.
That is accomplished by buffering changes to one line and applying them afterwards.
Other than that, we are just updating specific lines.
### Find and replace
We iterate through the file, finding incremental indexes.
We go through each of these lines and search the desired substring.
Then a find just moves the cursor there.
A replace does an update on this line, changing the substring to something else.

11
Makefile Normal file
View file

@ -0,0 +1,11 @@
CPP_FILES = \
main.cpp \
treap.cpp \
selection.cpp \
editor.cpp
HPP_FILES = \
everything.hpp
all: $(CPP_FILES) $(HPP_FILES)
g++ $(CPP_FILES) -lncurses -o ze

View file

@ -1,3 +1,61 @@
# editor # Ze ed
Deez Like deez, but backwards.
Ze is a terminal [vim](https://www.vim.org/)-like text editor.
It uses a *treap* data structure to represent file by lines and remembers versions with added *semi-persistence*.
## Dependencies
* `ncurses` c library
## Build
Described in the `Makefile`
## Usage
`ze <filename>`
## Modes
Ze has three modes within which you can edit a file - *normal*, *insert* and *select*.
Those represent what the user is currently doing:
* *insert* mode: Writing text
* *select* mode: Operations on a selection
* *normal* mode: Everything else
## Default keybinds
`<char>` - Action
### Normal mode
* `h`,`j`,`k`,`l` - Vim-like movement
* `g` - Goto a specified line
* `G` - Goto the last line
* `d` - Remove current line
* `x` - Remove character under cursor
* `o` - Insert empty line before the current
* `q` - Quit
* `w` - Save
* `u` - Undo latest editation action
* `f` - Find next appearance a specified string
* `n` - Repeat last find
* `s` - Find-and-replace
* `r` - Repeat last find-and-replace
* `p` - Paste selection
* `i` - Change into insert mode
* `v` - Change into select mode
### Insert mode
* `<ESC>` - Change into normal mode
* Anything else is written under the cursor
### Select mode
* `h`,`j`,`k`,`l` - Vim-like movement
* `g` - Goto a specified line
* `G` - Goto the last line
* `v` - Copy selected text
* `x` - Remove selected text
* `<ESC>` - Change into normal mode

View file

@ -9,7 +9,7 @@ void Editor::save() {
} }
for(count_type i = 0; i < file.size(); ++i) for(count_type i = 0; i < file.size(); ++i)
outfile << file.get_line(i, 0) << std::endl; outfile << file.get_line(i, false) << '\n';
} }
// Clear line // Clear line
@ -39,8 +39,10 @@ string Editor::get_input(string prompt, F func) {
string s = ""; string s = "";
char ch = getch(); char ch = getch();
while((ch != ENTER && func(ch)) || ch == BS) { while((ch != ENTER && func(ch)) || ch == BS) {
if(ch == BS) if(ch == BS) {
s.pop_back(); if(s.size())
s.pop_back();
}
else else
s += ch; s += ch;
print_text(cur.r, prompt+": " + s); print_text(cur.r, prompt+": " + s);
@ -55,13 +57,14 @@ string Editor::get_string(string prompt) {
} }
// Taking user input - number // Taking user input - number
count_type Editor::get_number(string prompt) { count_type Editor::get_number(string prompt) {
return stoi(get_input(prompt, [](char ch) { return ch >= '0' && ch <= '9'; })); string inp = get_input(prompt, [](char ch) { return ch >= '0' && ch <= '9'; });
if(inp.size() > 0)
return stoi(inp);
else
return 0;
} }
// TODO undo
// Jump to end of line // Jump to end of line
void Editor::jump_line_end() { void Editor::jump_line_end() {
count_type line_size = file.get_line(file_offset+cur.r).size(); count_type line_size = file.get_line(file_offset+cur.r).size();
@ -79,7 +82,8 @@ void Editor::move_cursor(char ch) {
break; break;
case 'j': case 'j':
if(file_offset + cur.r >= file.size()-1) break; if(file_offset + cur.r >= file.size()-1)
break;
if(cur.r < LINES-1) { if(cur.r < LINES-1) {
cur.r += 1; cur.r += 1;
move(cur); move(cur);
@ -129,7 +133,7 @@ void Editor::jump(count_type r) {
} }
// Find next string appearance // Find next string appearance
std::pair<bool, position> Editor::find(string text) { find_result Editor::find(string text) {
count_type len = text.size(); count_type len = text.size();
count_type file_size = file.size(); count_type file_size = file.size();
@ -139,10 +143,16 @@ std::pair<bool, position> Editor::find(string text) {
count_type start = ((_r == 0) ? (cur.c+1) : 0); count_type start = ((_r == 0) ? (cur.c+1) : 0);
count_type pos = file.get_line(r, 1).find(text, start); count_type pos = file.get_line(r, 1).find(text, start);
if(pos != string::npos) if(pos != string::npos)
return {true,{r, pos}}; return {true, {r, pos}};
} }
return {false, position()}; 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() { bool Editor::take_action() {
move(cur); move(cur);
@ -183,9 +193,15 @@ bool Editor::take_action() {
case 'w': case 'w':
save(); save();
break; break;
case 'u':
if(file.version() > 0) {
jump({file.undo(), 0});
print_file(file_offset);
}
break;
case 'i': case 'i':
mode = INSERT; mode = INSERT;
current_insert = file.get_line(file_offset + cur.r, 0); current_insert = file.get_line(file_offset + cur.r, false);
break; break;
case 'v': case 'v':
mode = SELECT; mode = SELECT;
@ -196,9 +212,9 @@ bool Editor::take_action() {
case 'n': case 'n':
{ {
auto result = find(last_find); auto result = find(last_find);
if(!result.first) if(!result.found)
break; break;
jump(result.second); jump(result.pos);
} }
print_file(file_offset); print_file(file_offset);
break; break;
@ -208,18 +224,19 @@ bool Editor::take_action() {
case 'r': case 'r':
{ {
auto result = find(last_find); auto result = find(last_find);
if(!result.first) if(!result.found)
break; break;
jump(result.second); jump(result.pos);
replace(cur + file_offset, last_find.size(), last_replace); replace(cur + file_offset, last_find.size(), last_replace);
} }
print_file(file_offset); print_file(file_offset);
break; break;
case 'p': case 'p':
selection.paste_selection(cur + file_offset); file.compress_versions(selection.paste_selection(cur + file_offset));
print_file(file_offset); print_file(file_offset);
break; break;
default: default:
//print_text(cur.r, "Unknown operation.");
break; break;
} }
break; break;
@ -227,19 +244,23 @@ bool Editor::take_action() {
switch(ch) { switch(ch) {
case ESC: case ESC:
mode = NORMAL; mode = NORMAL;
file.set_line(cur.r, current_insert); file.update(cur.r, current_insert);
print_text(cur.r, substitute_tabs(current_insert)); print_text(cur.r, substitute_tabs(current_insert));
current_insert = ""; current_insert = "";
break; break;
case BS: case BS:
if(cur.c > 0) { if(cur.c > 0) {
cur.c -= 1; count_type new_c = cur.c;
current_insert.erase(cur.c - get_tab_offset(cur.c, current_insert), 1); do new_c--;
while(new_c > 0 && get_tab_offset(new_c, current_insert) > get_tab_offset(new_c-1, current_insert));
current_insert.erase(cur.c-1 - get_tab_offset(cur.c-1, current_insert), 1);
cur.c = new_c;
} }
print_text(cur.r, substitute_tabs(current_insert)); print_text(cur.r, substitute_tabs(current_insert));
break; break;
case ENTER: case ENTER:
file.set_line(cur.r, current_insert); file.update(cur.r, current_insert);
file.split_line(cur + file_offset); file.split_line(cur + file_offset);
cur.r++; cur.c = 0; move(cur); cur.r++; cur.c = 0; move(cur);
current_insert = file.get_line(cur.r); current_insert = file.get_line(cur.r);
@ -267,12 +288,16 @@ bool Editor::take_action() {
jump(get_number("line number")); jump(get_number("line number"));
print_file(file_offset); print_file(file_offset);
break; break;
case 'G':
jump(file.size());
print_file(file_offset);
break;
case 'v': case 'v':
selection.copy_selection(cur + file_offset); selection.copy_selection(cur + file_offset);
mode = NORMAL; mode = NORMAL;
break; break;
case 'x': 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()) if(file_offset + cur.r >= file.size())
jump(cur + file_offset); jump(cur + file_offset);
print_file(file_offset); print_file(file_offset);

View file

@ -1,13 +1,16 @@
#pragma once #pragma once
#include <ncurses.h> #include <ncurses.h>
#include <vector> #include <vector>
#include <stack>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#define TAB_SIZE 8 #define TAB_SIZE 8
constexpr int RANDOM_SEED = 666;
using std::string; using std::string;
using std::vector; using std::vector;
using std::stack;
typedef unsigned long long count_type; typedef unsigned long long count_type;
// Position // Position
@ -28,43 +31,75 @@ struct line {
count_type priority, size; // Treap innards count_type priority, size; // Treap innards
string text; // Content string text; // Content
line *left, *right; // Sons 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) : 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();
}
}; };
typedef std::pair<line*, line*> two_lines; struct change { count_type row, count; };
// Treap data structure // Treap data structure
class Treap { class Treap {
line *root; stack<change> changes;
stack<line*> root;
line* get_root() { return root.top(); };
two_lines split(line *l, count_type k); line* insert(line *l, count_type k, string s, bool new_version);
line* join(line *a, line *b); line* remove(line *l, count_type k);
line* join(two_lines two) { return join(two.first, two.second); } line* update(line *l, count_type k, string s);
line* pick_higher(line *l);
void delete_version(line *l);
count_type get_size(line *l); count_type get_size(line *l);
count_type get_priority(line *l);
line* find(line *l, count_type k); line* find(line *l, count_type k);
line* find(count_type k);
count_type bulk_find(vector<string> *vec, line *l, count_type k, count_type count); count_type bulk_find(vector<string> *vec, line *l, count_type k, count_type count);
public:
public:
// General operations // General operations
Treap() { srand(120); root = nullptr; } Treap() { srand(RANDOM_SEED); root.push(nullptr); }
count_type size() { return get_size(root); } count_type size() { return get_size(get_root()); }
void clear(); void clear();
// Line get operations // Versioning
string get_line(count_type r, bool substitute_tab = 1); 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<string> bulk_get_line(count_type r, count_type count); vector<string> 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)); } count_type get_tab_offset(position p) { return ::get_tab_offset(p.c, get_line(p.r, 0)); }
// Line set operations // Line meta-operations
void split_line(position p); void insert(count_type k, string s, bool new_version = true);
void insert(count_type k, string s);
void remove(count_type k); void remove(count_type k);
void set_line(count_type k, string s) { find(k)->text = s; } void update(count_type k, string s);
// 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 insert(position p, string t);
void remove(position p, count_type len = 1); void remove(position p, count_type len = 1);
}; };
@ -72,18 +107,20 @@ public:
//// Selection //// Selection
class Selection { class Selection {
Treap *file; Treap *file;
Treap content; vector<string> content;
template <typename F, typename G> template <typename F, typename G>
void apply_on_selection(position p, F func, G func2); void apply_on_selection(position p, F func, G func2);
public: public:
Selection() {} Selection() {}
Selection(Treap *_file) { file = _file; } Selection(Treap *_file) { file = _file; }
position pos; // last selection position position pos;
count_type size() { return file->size(); }
void remove_selection(position p);
void copy_selection(position p); void copy_selection(position p);
void paste_selection(position p); count_type remove_selection(position p);
count_type paste_selection(position p);
}; };
@ -93,6 +130,7 @@ public:
#define BS 127 #define BS 127
#define adds(s) addstr(s.c_str()) #define adds(s) addstr(s.c_str())
enum mode_type { INSERT, NORMAL, SELECT }; enum mode_type { INSERT, NORMAL, SELECT };
struct find_result { bool found; position pos; };
class Editor{ class Editor{
// Editor state // Editor state
@ -132,8 +170,8 @@ class Editor{
void jump(position p) { jump(p.r); cur.c = p.c; } void jump(position p) { jump(p.r); cur.c = p.c; }
// Find and replace // Find and replace
std::pair<bool, position> find(string text); find_result find(string text);
void replace(position p, count_type length, string text) { file.remove(p, length); file.insert(p, text); } void replace(position p, count_type length, string text);
public: public:
Editor(string _filename, Treap &_file) { Editor(string _filename, Treap &_file) {

View file

@ -49,9 +49,10 @@ int main(int argc, const char *argv[]) {
// Load file // Load file
Treap file; Treap file;
string line; string line;
while (std::getline(infile, line)) while (std::getline(infile, line))
file.insert(file.size(), line); file.insert(file.size(), line, false);
// Init // Init
initscr(); initscr();

View file

@ -24,34 +24,55 @@ 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; count_type size = ((start.r == end.r) ? end.c+1 : start_line.size()) - start.c;
func2(start.r, start.c, size, file, &content); 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 // Copying selection
void Selection::copy_selection(position p) { void Selection::copy_selection(position p) {
// Insert in the wrong order
apply_on_selection(p, apply_on_selection(p,
[](count_type r, Treap *file, Treap *sel){ sel->insert(0, file->get_line(r, false)); }, [](count_type r, Treap *file, vector<string> *sel){ sel->push_back(file->get_line(r, false)); },
[](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)); } [](count_type r, count_type start_c, count_type size, Treap *file, vector<string> *sel) { sel->push_back(file->get_line(r, false).substr(start_c - file->get_tab_offset({r, start_c}), size)); }
); );
// Reverse order of the vector
for(count_type i = 0; i < content.size()/2; ++i) {
string tmp = content[content.size()-1 - i];
content[content.size()-1 - i] = content[i];
content[i] = tmp;
}
}
// Removing selection
count_type Selection::remove_selection(position p) {
apply_on_selection(p,
[](count_type r, Treap *file, vector<string> *sel){ file->remove(r); },
[](count_type r, count_type start_c, count_type size, Treap *file, vector<string> *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 // Pasting selection
void Selection::paste_selection(position p) { count_type Selection::paste_selection(position p) {
if(content.size() == 0) return; if(content.size() == 0) return 0;
// Insert last line inside of this // Insert last line inside of this
file->insert(p, content.get_line(content.size()-1, false)); file->insert(p, content.back());
if(content.size() == 1) return; if(content.size() == 1) return 1;
// Insert first line in front and split // Insert first line in front and split
file->split_line(p); file->split_line(p, false);
file->insert(p, content.get_line(0, false)); file->insert(p, content.front());
if(content.size() == 2) return; if(content.size() == 2) return 4;
// Insert lines // Insert lines
for(count_type i = content.size()-2; i > 0; --i) 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[i]);
return content.size()+2;
} }

311
treap.cpp
View file

@ -2,99 +2,249 @@
// Get size uf a subtreap // Get size uf a subtreap
count_type Treap::get_size(line *l) { count_type Treap::get_size(line *l) {
if(l == nullptr) return 0; if(l == nullptr)
return 0;
return l->size; return l->size;
} }
count_type Treap::get_priority(line *l) {
// Split treap by k-th element if(l == nullptr)
two_lines Treap::split(line *l, count_type k) { return 0;
if(l == nullptr) return {nullptr, nullptr}; return l->priority;
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};
}
} }
// Join two treaps // Treap node rotations
line* Treap::join(line *a, line *b) { line *rotate_left(line *l) {
if(a == nullptr) return b; line *r = l->right, *mid = r->left;
if(b == nullptr) return a;
if(a->priority < b->priority) { // Switch
a->size += get_size(b); l->set_right(mid);
a->right = join(a->right, b); r->set_left(l);
return a;
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);
// Update the node
if(new_version)
l = new line(l->priority, l->text, son, l->right, son);
else
l->set_left(son);
// Balance heap-like structure
if(get_priority(l->left) > get_priority(l))
l = rotate_right(l);
} }
// Insert into right subtree
else { else {
b->size += get_size(a); line *son = insert(l->right, k - (get_size(l->left)+1), s, new_version);
b->left = join(a, b->left);
return b; // Update the node
if(new_version)
l = new line(l->priority, l->text, l->left, son, son);
else
l->set_right(son);
// Balance heap-like structure
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 (the easy cases)
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 to get the son higher
rotate_right(&l_copy);
// Remove under
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 to get the son higher
rotate_left(&l_copy);
// Remove under
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) {
if(k < get_size(l->left)) {
line *son = update(l->left, k, s);
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);
return new line(l->priority, l->text, l->left, son, son);
} }
} }
// Find k-th line of file // Find k-th line of file
line* Treap::find(line *l, count_type k) { 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); return find(l->left, k);
else if(k == get_size(l->left)+1) else if(k == get_size(l->left))
return l; return l;
else else
return find(l->right, k - (1+get_size(l->left)) ); return find(l->right, k - (1+get_size(l->left)) );
} }
line* Treap::find(count_type k) {
if(k >= root->size) // Version control
return nullptr; void Treap::delete_version(line *l) {
// Don't find index k, but k-th line -> +1 if(l == nullptr)
return find(root, k+1); 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) {
// Remove all compressed change info
for(count_type i = 1; i < count; ++i)
changes.pop();
// Convert them into one grand-info
if(count > 0)
changes.top().count = count;
} }
// File access // File access
void Treap::clear() { void Treap::clear() {
while(size() > 0) { // Clear all versions
auto two = split(root, 1); while(root.size() > 1)
root = two.second; undo();
delete two.first;
} // Clear original file
while(size() > 0)
remove(0);
} }
// Line insert // Line insert
void Treap::insert(count_type k, string s) { void Treap::insert(count_type k, string s, bool new_version) {
line *l = new line(rand(), s); if(k > size()) {
std::cerr << "Inserting out of file range\n";
if(root == nullptr) {
root = l;
return; return;
} }
auto two = split(root, k); line* new_root = insert(get_root(), k, s, new_version);
two.first = join(two.first, l);
root = join(two); if(new_version) {
root.push(new_root);
changes.push({k, 1});
}
else
root.top() = new_root;
} }
// Line removal // Line removal
void Treap::remove(count_type k) { void Treap::remove(count_type k) {
auto two = split(root, k+1); if(k >= size()) {
auto first_split = split(two.first, k); std::cerr << "Removing out of file range\n";
delete first_split.second; return;
two.first = first_split.first; }
root = join(two);
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) {
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);
root.push(new_root);
changes.push({k, 1});
} }
// Accessing the file // Accessing the file
string Treap::get_line(count_type r, bool substitute_tab) { string Treap::get_line(count_type r, bool substitute_tab) {
string line = find(r)->text; string line = find(get_root(), r)->text;
if(substitute_tab) if(substitute_tab)
return substitute_tabs(line); return substitute_tabs(line);
else else
@ -104,7 +254,7 @@ string Treap::get_line(count_type r, bool substitute_tab) {
count_type Treap::bulk_find(vector<string> *vec, line *l, count_type k, count_type count) { count_type Treap::bulk_find(vector<string> *vec, line *l, count_type k, count_type count) {
if(l == nullptr) return count; if(l == nullptr) return count;
// Wanted vertex is on the left // Wanted vertex is on the left (this may be wanted afterwards)
if(k <= get_size(l->left)) { if(k <= get_size(l->left)) {
count_type r = bulk_find(vec, l->left, k, count); count_type r = bulk_find(vec, l->left, k, count);
if(r > 0) if(r > 0)
@ -115,7 +265,7 @@ count_type Treap::bulk_find(vector<string> *vec, line *l, count_type k, count_ty
return 0; return 0;
} }
// This is the wanted vertex // This is a wanted vertex
else if(k == get_size(l->left)+1) { else if(k == get_size(l->left)+1) {
vec->push_back(substitute_tabs(l->text)); vec->push_back(substitute_tabs(l->text));
if(count > 1) if(count > 1)
@ -123,26 +273,53 @@ count_type Treap::bulk_find(vector<string> *vec, line *l, count_type k, count_ty
else else
return 0; return 0;
} }
// Wanted vertex is on the right (this is never wanted)
else else
return bulk_find(vec, l->right, k - (1+get_size(l->left)), count); return bulk_find(vec, l->right, k - (1+get_size(l->left)), count);
} }
// Access multiple adjacent vertices // Access multiple adjacent vertices
vector<string> Treap::bulk_get_line(count_type r, count_type count) { vector<string> Treap::bulk_get_line(count_type r, count_type count) {
vector<string> ret(0); vector<string> ret(0);
bulk_find(&ret, root, r+1, count); bulk_find(&ret, get_root(), r+1, count);
return ret; return ret;
} }
void Treap::insert(position p, string t) { 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) { 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 // Split line
void Treap::split_line(position p) { void Treap::split_line(position p, bool should_compress) {
string line = get_line(p.r); string line = get_line(p.r, false);
string newline = line.substr(p.c, line.size()); count_type real_c = p.c - get_tab_offset(p);
remove(p, line.size()-p.c); string newline = line.substr(real_c, line.size());
if(line.size()-real_c > 0)
remove(p, line.size()-real_c);
insert(p.r+1, newline); 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);
} }