Compare commits
10 commits
34cc427533
...
9bea4d0147
Author | SHA1 | Date | |
---|---|---|---|
9bea4d0147 | |||
3cf1f2f52b | |||
f874852629 | |||
02dc2b9d7c | |||
131a0d3384 | |||
4696eca6c3 | |||
b1b0d89e71 | |||
e02dd9e729 | |||
495bf542cf | |||
9fd135e540 |
8 changed files with 570 additions and 131 deletions
108
DOC.md
Normal file
108
DOC.md
Normal 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
11
Makefile
Normal 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
|
62
README.md
62
README.md
|
@ -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
|
||||
|
|
67
editor.cpp
67
editor.cpp
|
@ -9,7 +9,7 @@ void Editor::save() {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -39,8 +39,10 @@ string Editor::get_input(string prompt, F func) {
|
|||
string s = "";
|
||||
char ch = getch();
|
||||
while((ch != ENTER && func(ch)) || ch == BS) {
|
||||
if(ch == BS)
|
||||
s.pop_back();
|
||||
if(ch == BS) {
|
||||
if(s.size())
|
||||
s.pop_back();
|
||||
}
|
||||
else
|
||||
s += ch;
|
||||
print_text(cur.r, prompt+": " + s);
|
||||
|
@ -55,13 +57,14 @@ string Editor::get_string(string prompt) {
|
|||
}
|
||||
// Taking user input - number
|
||||
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
|
||||
void Editor::jump_line_end() {
|
||||
count_type line_size = file.get_line(file_offset+cur.r).size();
|
||||
|
@ -79,7 +82,8 @@ void Editor::move_cursor(char ch) {
|
|||
break;
|
||||
|
||||
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) {
|
||||
cur.r += 1;
|
||||
move(cur);
|
||||
|
@ -129,7 +133,7 @@ void Editor::jump(count_type r) {
|
|||
}
|
||||
|
||||
// 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 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 pos = file.get_line(r, 1).find(text, start);
|
||||
if(pos != string::npos)
|
||||
return {true,{r, pos}};
|
||||
return {true, {r, pos}};
|
||||
}
|
||||
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);
|
||||
|
@ -183,9 +193,15 @@ 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, 0);
|
||||
current_insert = file.get_line(file_offset + cur.r, false);
|
||||
break;
|
||||
case 'v':
|
||||
mode = SELECT;
|
||||
|
@ -196,9 +212,9 @@ bool Editor::take_action() {
|
|||
case 'n':
|
||||
{
|
||||
auto result = find(last_find);
|
||||
if(!result.first)
|
||||
if(!result.found)
|
||||
break;
|
||||
jump(result.second);
|
||||
jump(result.pos);
|
||||
}
|
||||
print_file(file_offset);
|
||||
break;
|
||||
|
@ -208,18 +224,19 @@ bool Editor::take_action() {
|
|||
case 'r':
|
||||
{
|
||||
auto result = find(last_find);
|
||||
if(!result.first)
|
||||
if(!result.found)
|
||||
break;
|
||||
jump(result.second);
|
||||
jump(result.pos);
|
||||
replace(cur + file_offset, last_find.size(), last_replace);
|
||||
}
|
||||
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:
|
||||
//print_text(cur.r, "Unknown operation.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -227,19 +244,23 @@ 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;
|
||||
case BS:
|
||||
if(cur.c > 0) {
|
||||
cur.c -= 1;
|
||||
current_insert.erase(cur.c - get_tab_offset(cur.c, current_insert), 1);
|
||||
count_type new_c = cur.c;
|
||||
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));
|
||||
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);
|
||||
|
@ -267,12 +288,16 @@ bool Editor::take_action() {
|
|||
jump(get_number("line number"));
|
||||
print_file(file_offset);
|
||||
break;
|
||||
case 'G':
|
||||
jump(file.size());
|
||||
print_file(file_offset);
|
||||
break;
|
||||
case 'v':
|
||||
selection.copy_selection(cur + file_offset);
|
||||
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);
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
#pragma once
|
||||
#include <ncurses.h>
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#define TAB_SIZE 8
|
||||
constexpr int RANDOM_SEED = 666;
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::stack;
|
||||
typedef unsigned long long count_type;
|
||||
|
||||
// Position
|
||||
|
@ -28,43 +31,75 @@ 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();
|
||||
}
|
||||
};
|
||||
typedef std::pair<line*, line*> two_lines;
|
||||
struct change { count_type row, count; };
|
||||
|
||||
// Treap data structure
|
||||
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* 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);
|
||||
|
||||
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<string> *vec, line *l, count_type k, count_type count);
|
||||
public:
|
||||
|
||||
public:
|
||||
// General operations
|
||||
Treap() { srand(120); root = nullptr; }
|
||||
count_type size() { return get_size(root); }
|
||||
Treap() { srand(RANDOM_SEED); root.push(nullptr); }
|
||||
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<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)); }
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
@ -72,18 +107,20 @@ public:
|
|||
//// Selection
|
||||
class Selection {
|
||||
Treap *file;
|
||||
Treap content;
|
||||
vector<string> content;
|
||||
|
||||
template <typename F, typename G>
|
||||
void apply_on_selection(position p, F func, G func2);
|
||||
public:
|
||||
Selection() {}
|
||||
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 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 adds(s) addstr(s.c_str())
|
||||
enum mode_type { INSERT, NORMAL, SELECT };
|
||||
struct find_result { bool found; position pos; };
|
||||
|
||||
class Editor{
|
||||
// Editor state
|
||||
|
@ -132,8 +170,8 @@ class Editor{
|
|||
void jump(position p) { jump(p.r); cur.c = p.c; }
|
||||
|
||||
// Find and replace
|
||||
std::pair<bool, position> find(string text);
|
||||
void replace(position p, count_type length, string text) { file.remove(p, length); file.insert(p, text); }
|
||||
find_result find(string text);
|
||||
void replace(position p, count_type length, string text);
|
||||
|
||||
public:
|
||||
Editor(string _filename, Treap &_file) {
|
||||
|
|
3
main.cpp
3
main.cpp
|
@ -49,9 +49,10 @@ int main(int argc, const char *argv[]) {
|
|||
|
||||
// Load file
|
||||
Treap file;
|
||||
|
||||
string line;
|
||||
while (std::getline(infile, line))
|
||||
file.insert(file.size(), line);
|
||||
file.insert(file.size(), line, false);
|
||||
|
||||
// Init
|
||||
initscr();
|
||||
|
|
|
@ -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;
|
||||
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) {
|
||||
// Insert in the wrong order
|
||||
apply_on_selection(p,
|
||||
[](count_type r, Treap *file, Treap *sel){ sel->insert(0, 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, 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, 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
|
||||
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;
|
||||
file->insert(p, content.back());
|
||||
if(content.size() == 1) return 1;
|
||||
|
||||
// Insert first line in front and split
|
||||
file->split_line(p);
|
||||
file->insert(p, content.get_line(0, false));
|
||||
if(content.size() == 2) return;
|
||||
file->split_line(p, false);
|
||||
file->insert(p, content.front());
|
||||
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[i]);
|
||||
|
||||
return content.size()+2;
|
||||
}
|
||||
|
|
311
treap.cpp
311
treap.cpp
|
@ -2,99 +2,249 @@
|
|||
|
||||
// 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);
|
||||
|
||||
// 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 {
|
||||
b->size += get_size(a);
|
||||
b->left = join(a, b->left);
|
||||
return b;
|
||||
line *son = insert(l->right, k - (get_size(l->left)+1), s, new_version);
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
||||
// Version control
|
||||
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) {
|
||||
// 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
|
||||
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) {
|
||||
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
|
||||
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)
|
||||
return substitute_tabs(line);
|
||||
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) {
|
||||
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)) {
|
||||
count_type r = bulk_find(vec, l->left, k, count);
|
||||
if(r > 0)
|
||||
|
@ -115,7 +265,7 @@ count_type Treap::bulk_find(vector<string> *vec, line *l, count_type k, count_ty
|
|||
return 0;
|
||||
}
|
||||
|
||||
// This is the wanted vertex
|
||||
// This is a wanted vertex
|
||||
else if(k == get_size(l->left)+1) {
|
||||
vec->push_back(substitute_tabs(l->text));
|
||||
if(count > 1)
|
||||
|
@ -123,26 +273,53 @@ count_type Treap::bulk_find(vector<string> *vec, line *l, count_type k, count_ty
|
|||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Wanted vertex is on the right (this is never wanted)
|
||||
else
|
||||
return bulk_find(vec, l->right, k - (1+get_size(l->left)), count);
|
||||
}
|
||||
// Access multiple adjacent vertices
|
||||
vector<string> Treap::bulk_get_line(count_type r, count_type count) {
|
||||
vector<string> 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 real_c = p.c - get_tab_offset(p);
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue