zeed/main.cpp
2025-04-20 11:56:02 +02:00

424 lines
8.8 KiB
C++

#include <ncurses.h>
#include <curses.h>
#include <string>
#include <fstream>
#include "treap.hpp"
using std::string;
#define ESC 27
#define ENTER 10
#define BS 127
#define adds(s) addstr(s.c_str())
enum mode_type { INSERT, NORMAL, SELECT };
struct position {
count_type r, c;
position operator+(count_type offset) { return {r + offset, c}; }
};
// Utilities
count_type min(count_type a, count_type b) {
if(a <= b)
return a;
return b;
}
count_type max(count_type a, count_type b) {
if(a >= b)
return a;
return b;
}
// Global variables
treap file;
position cur = {0, 0};
count_type file_offset = 0;
position clp;
treap selection;
string last_find, last_replace;
// Accessing the file
string get_line(count_type r, bool substitute_tab = true) {
string line = file.get(r);
if(!substitute_tab)
return line;
for(count_type i = 0; i < line.size(); ++i)
if(line[i] == '\t')
line[i] = ' ';
return line;
}
void set(position p, char ch) { file.find(p.r)->text[p.c] = 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, 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,len); }
void remove(count_type r) { file.remove(r); }
void append(string t) { file.append(t); }
// 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 < file.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 < file.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(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();
}
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;
}
// Copying
void copy_selection(position p = cur + file_offset) {
// Determine first and last selected position
position start = p, end = clp;
if(clp.r < cur.r || (clp.r == cur.r && clp.c < cur.c))
start = clp, end = p;
// Clear previous selection
selection.clear();
// Add all complete lines in between
for(count_type r = start.r+1; r < end.r; ++r)
selection.append(get_line(r));
// Add first line end
string start_line = get_line(start.r);
count_type size = ((start.r == end.r) ? end.c+1 : start_line.size()) - start.c;
string start_text = start_line.substr(start.c, size);
selection.insert(0, start_text);
// Add last line start
if(start.r < end.r) {
string end_line = get_line(end.r);
string end_text = end_line.substr(0, end.c+1);
selection.append(end_text);
}
}
// Pasting
void paste_selection(position p = cur + file_offset) {
if(selection.size() == 0) return;
// Insert last line inside of this
insert(p, selection.get(selection.size()-1));
if(selection.size() == 1) return;
// Insert first line in front and split
split_line(p);
insert(p, selection.get(0));
if(selection.size() == 2) return;
// Insert lines
for(count_type i = selection.size()-2; i > 0; --i)
new_line(p.r, selection.get(i));
}
// 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 >= file.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 - file_offset > LINES || r < file_offset)
file_offset = max(0, min(file.size()-LINES, r));
cur.r = max(0, r - file_offset);
jump_line_end();
}
// 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 = file.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)
return 1;
string filename = argv[1];
if(load(filename))
return 1;
// Init
initscr();
refresh();
print_file();
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 'd':
remove(file_offset + cur.r);
print_file(file_offset + cur.r);
break;
case 'x':
remove(cur + file_offset);
print_line(file_offset + cur.r);
break;
case 'o':
new_line(file_offset + cur.r);
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;
clp = cur + file_offset;
break;
case 'f':
last_find = get_string("to find");
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");
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':
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;
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':
copy_selection(cur + file_offset);
mode = NORMAL;
break;
default:
mode = NORMAL;
break;
}
break;
}
refresh();
}
// End
endwin();
return 0;
}