406 lines
8.5 KiB
C++
406 lines
8.5 KiB
C++
#include "editor.hpp"
|
|
|
|
#define TAB_SIZE 2
|
|
|
|
enum mode_type { INSERT, NORMAL, SELECT };
|
|
|
|
// Global variables
|
|
treap file; // file representation
|
|
position cur = {0, 0}; // cursor position
|
|
count_type file_offset = 0; // terminal file offset
|
|
|
|
// Accessing the file
|
|
string get_line(count_type r, bool substitute_tab) {
|
|
string line = file.get(r);
|
|
if(!substitute_tab)
|
|
return line;
|
|
string ret = "";
|
|
for(count_type i = 0; i < line.size(); ++i) {
|
|
if(line[i] == '\t')
|
|
for(int j = 0; j < TAB_SIZE; j++)
|
|
ret += ' ';
|
|
else
|
|
ret += line[i];
|
|
}
|
|
return ret;
|
|
}
|
|
count_type get_tab_offset(position p) {
|
|
count_type tab_offset = 0;
|
|
string line = get_line(p.r, false);
|
|
for(count_type i = 0; i + tab_offset < p.c; ++i) {
|
|
if(line[i] == '\t')
|
|
tab_offset += TAB_SIZE - 1;
|
|
if(i + tab_offset > p.c)
|
|
tab_offset -= i + tab_offset - p.c;
|
|
}
|
|
return tab_offset;
|
|
}
|
|
void set(position p, char ch) { file.find(p.r)->text[p.c-get_tab_offset(p)] = ch; }
|
|
void new_line(count_type r, string text = "") { file.insert(r, text); }
|
|
void insert(position p, string t) { file.find(p.r)->text.insert(p.c-get_tab_offset(p), t); }
|
|
void insert(position p, char ch) { insert(p, string{ch}); }
|
|
void remove(position p, count_type len=1) { file.find(p.r)->text.erase(p.c-get_tab_offset(p),len); }
|
|
void remove(count_type r) { file.remove(r); }
|
|
void append(string t) { file.append(t); }
|
|
count_type get_size() { return file.size(); }
|
|
|
|
|
|
// Load file to buffer
|
|
bool load(string filename) {
|
|
std::ifstream infile(filename);
|
|
if(!infile.good())
|
|
return 1;
|
|
|
|
string line;
|
|
while (std::getline(infile, line))
|
|
append(line);
|
|
return 0;
|
|
}
|
|
// Save file from buffer
|
|
bool save(string filename) {
|
|
std::ofstream outfile(filename);
|
|
if(!outfile.good())
|
|
return 1;
|
|
|
|
for(count_type i = 0; i < get_size(); ++i)
|
|
outfile << get_line(i, 0) << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Clear line
|
|
void clear_line(count_type r) {
|
|
move(r - file_offset, 0);
|
|
clrtoeol();
|
|
}
|
|
// Print line
|
|
void print_line(count_type r) {
|
|
clear_line(r);
|
|
move(r - file_offset, 0);
|
|
adds(get_line(r));
|
|
}
|
|
// Print file content
|
|
void print_file(count_type start = 0) {
|
|
for(count_type i = start; i < file_offset + LINES; i++)
|
|
if(i < get_size())
|
|
print_line(i);
|
|
else
|
|
clear_line(i);
|
|
}
|
|
// Splitting lines
|
|
void split_line(position p) {
|
|
string line = get_line(p.r);
|
|
string newline = line.substr(p.c, line.size());
|
|
remove(p, line.size()-p.c);
|
|
new_line(p.r+1, newline);
|
|
}
|
|
// Print input
|
|
void print_input(string text) {
|
|
clear_line(file_offset + cur.r);
|
|
move(cur.r, 0);
|
|
adds(text);
|
|
}
|
|
// Taking user input - number
|
|
count_type get_number(string prompt) {
|
|
print_input(prompt+": ");
|
|
char ch = '0';
|
|
string s = "";
|
|
ch = getch();
|
|
while(ch >= '0' && ch <= '9') {
|
|
s += ch;
|
|
print_input(prompt+": " + s);
|
|
ch = getch();
|
|
}
|
|
|
|
if(s.size() == 0)
|
|
return file_offset + cur.r;
|
|
|
|
return stoi(s);
|
|
}
|
|
// Taking user input - string
|
|
string get_string(string prompt) {
|
|
print_input(prompt+": ");
|
|
char ch;
|
|
string s = "";
|
|
ch = getch();
|
|
while(ch != ENTER) {
|
|
if(ch == BS)
|
|
s.pop_back();
|
|
else
|
|
s += ch;
|
|
print_input(prompt+": " + s);
|
|
ch = getch();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
// TODO undo
|
|
|
|
|
|
// Jump to end of line
|
|
void jump_line_end() {
|
|
count_type line_size = get_line(file_offset+cur.r).size();
|
|
if(cur.c > line_size)
|
|
cur.c = line_size;
|
|
}
|
|
// Move cursor within window
|
|
void move(position p) { move(p.r, p.c); }
|
|
// Cursor movement
|
|
void move_cursor(char ch) {
|
|
switch(ch) {
|
|
case 'h':
|
|
if(cur.c > 0)
|
|
move(cur.r, --cur.c);
|
|
break;
|
|
|
|
case 'j':
|
|
if(file_offset + cur.r >= get_size()-1) break;
|
|
if(cur.r < LINES-1) {
|
|
cur.r += 1;
|
|
move(cur);
|
|
jump_line_end();
|
|
}
|
|
else if(cur.r == LINES-1) {
|
|
file_offset++;
|
|
jump_line_end();
|
|
print_file();
|
|
}
|
|
break;
|
|
|
|
case 'k':
|
|
if(cur.r > 0) {
|
|
cur.r -= 1;
|
|
move(cur);
|
|
jump_line_end();
|
|
}
|
|
else if(file_offset > 0) {
|
|
file_offset--;
|
|
jump_line_end();
|
|
print_file();
|
|
}
|
|
break;
|
|
|
|
case 'l':
|
|
if(cur.c < get_line(file_offset+cur.r).size()) {
|
|
cur.c += 1;
|
|
move(cur);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// Jump to line
|
|
void jump(count_type r) {
|
|
if(r >= get_size()) r = get_size()-1;
|
|
else if(r < 0) r = 0;
|
|
|
|
if(r < file_offset)
|
|
file_offset = r;
|
|
else if(r >= file_offset + LINES)
|
|
file_offset = r - LINES+1;
|
|
|
|
cur.r = r - file_offset;
|
|
jump_line_end();
|
|
move(cur);
|
|
}
|
|
// Jump to position
|
|
void jump(position p) {
|
|
jump(p.r);
|
|
cur.c = p.c;
|
|
}
|
|
|
|
// Find next string appearance
|
|
std::pair<bool, position> find(string text) {
|
|
count_type len = text.size();
|
|
count_type file_size = get_size();
|
|
|
|
for(count_type _r = 0; _r < file_size; ++_r) {
|
|
count_type r = (file_offset + cur.r + _r) % file_size;
|
|
|
|
count_type start = ((_r == 0) ? (cur.c+1) : 0);
|
|
count_type pos = get_line(r, 0).find(text, start);
|
|
if(pos != string::npos)
|
|
return {true,{r, pos}};
|
|
}
|
|
return {false, position()};
|
|
}
|
|
// Replace string appearance
|
|
void replace(position p, count_type length, string text) {
|
|
remove(p, length);
|
|
insert(p, text);
|
|
}
|
|
|
|
|
|
int main(int argc, char* argv[]) {
|
|
// Check valid filename and load
|
|
if(argc <= 1) {
|
|
std::cerr << "No input filename\n";
|
|
return 1;
|
|
}
|
|
string filename = argv[1];
|
|
if(load(filename)) {
|
|
std::cerr << "Bad input filename\n";
|
|
return 1;
|
|
}
|
|
|
|
// Init
|
|
initscr();
|
|
refresh();
|
|
print_file();
|
|
|
|
// Variables
|
|
string last_find, last_replace; // last find/replace inputs
|
|
Selection selection;
|
|
|
|
mode_type mode = NORMAL;
|
|
|
|
// Main loop
|
|
bool run = true;
|
|
while(run) {
|
|
move(cur);
|
|
char ch = getch();
|
|
switch(mode) {
|
|
case NORMAL:
|
|
print_line(file_offset + cur.r);
|
|
switch(ch) {
|
|
case 'h': case 'j': case 'k': case 'l':
|
|
move_cursor(ch);
|
|
break;
|
|
case 'g':
|
|
jump(get_number("line number"));
|
|
print_file(file_offset);
|
|
break;
|
|
case 'G':
|
|
jump(get_size());
|
|
print_file(file_offset);
|
|
break;
|
|
case 'd':
|
|
remove(file_offset + cur.r);
|
|
if(file_offset + cur.r >= get_size())
|
|
jump(cur + file_offset);
|
|
print_file(file_offset);
|
|
break;
|
|
case 'x':
|
|
remove(cur + file_offset);
|
|
print_line(file_offset + cur.r);
|
|
break;
|
|
case 'o':
|
|
new_line(file_offset + cur.r);
|
|
jump_line_end();
|
|
print_file(file_offset + cur.r);
|
|
break;
|
|
case 'q':
|
|
run = false;
|
|
break;
|
|
case 'w':
|
|
save(filename);
|
|
break;
|
|
case 'i':
|
|
mode = INSERT;
|
|
break;
|
|
case 'v':
|
|
mode = SELECT;
|
|
selection.pos = cur + file_offset;
|
|
break;
|
|
case 'f':
|
|
last_find = get_string("to find");
|
|
print_line(file_offset + cur.r);
|
|
case 'n':
|
|
{
|
|
auto result = find(last_find);
|
|
if(!result.first) {
|
|
print_line(file_offset + cur.r);
|
|
break;
|
|
}
|
|
jump(result.second);
|
|
}
|
|
print_file(file_offset + cur.r);
|
|
break;
|
|
case 's':
|
|
last_find = get_string("to find");
|
|
last_replace = get_string("to replace");
|
|
print_line(file_offset + cur.r);
|
|
case 'r':
|
|
{
|
|
auto result = find(last_find);
|
|
if(!result.first) {
|
|
print_line(file_offset + cur.r);
|
|
break;
|
|
}
|
|
jump(result.second);
|
|
replace(cur + file_offset, last_find.size(), last_replace);
|
|
}
|
|
print_file(file_offset + cur.r);
|
|
break;
|
|
case 'p':
|
|
selection.paste_selection(cur + file_offset);
|
|
print_file(file_offset + cur.r);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case INSERT:
|
|
switch(ch) {
|
|
case ESC:
|
|
mode = NORMAL;
|
|
print_line(file_offset + cur.r);
|
|
break;
|
|
case BS:
|
|
if(cur.c > 0) {
|
|
cur.c -= 1;
|
|
remove(cur + file_offset);
|
|
}
|
|
print_line(file_offset + cur.r);
|
|
break;
|
|
case ENTER:
|
|
split_line(cur + file_offset);
|
|
print_file(file_offset + cur.r++);
|
|
jump_line_end();
|
|
break;
|
|
case '\t':
|
|
insert(cur + file_offset, ch);
|
|
cur.c += TAB_SIZE;
|
|
print_line(file_offset + cur.r);
|
|
break;
|
|
default:
|
|
insert(cur + file_offset, ch);
|
|
cur.c += 1;
|
|
print_line(file_offset + cur.r);
|
|
break;
|
|
}
|
|
break;
|
|
case SELECT:
|
|
print_line(file_offset + cur.r);
|
|
switch(ch) {
|
|
case 'h': case 'j': case 'k': case 'l':
|
|
move_cursor(ch);
|
|
break;
|
|
case 'g':
|
|
jump(get_number("line number"));
|
|
print_file(file_offset);
|
|
break;
|
|
case 'v':
|
|
selection.copy_selection(cur + file_offset);
|
|
mode = NORMAL;
|
|
break;
|
|
case 'x':
|
|
selection.remove_selection(cur + file_offset);
|
|
if(file_offset + cur.r >= get_size())
|
|
jump(cur + file_offset);
|
|
print_file();
|
|
mode = NORMAL;
|
|
break;
|
|
default:
|
|
mode = NORMAL;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
refresh();
|
|
}
|
|
|
|
// End
|
|
endwin();
|
|
return 0;
|
|
}
|