commit
b1db1bcc8a
8 changed files with 480 additions and 0 deletions
@ -0,0 +1,2 @@ |
||||
.idea |
||||
build |
@ -0,0 +1,26 @@ |
||||
cmake_minimum_required(VERSION 3.7) |
||||
project(groove C CXX) |
||||
|
||||
set(CMAKE_CXX_STANDARD 14) |
||||
|
||||
include_directories(src) |
||||
|
||||
#set(SOURCE_FILES main.cpp) |
||||
file( |
||||
GLOB_RECURSE SOURCE_FILES |
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp |
||||
) |
||||
|
||||
file( |
||||
GLOB_RECURSE HEADER_FILES |
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/*.h |
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp |
||||
) |
||||
|
||||
include_directories( |
||||
${CMAKE_CURRENT_SOURCE_DIR}/src |
||||
) |
||||
|
||||
add_executable(groove main.cpp ${SOURCE_FILES} ${HEADER_FILES}) |
||||
|
||||
target_link_libraries(groove ncursesw) |
@ -0,0 +1,25 @@ |
||||
#include <iostream> |
||||
#include <ncurses/ncurses.hpp> |
||||
#include <Editor.hpp> |
||||
|
||||
int main(int argc, char* argv[]) { |
||||
groove::Editor editor; |
||||
if (argc > 1) |
||||
editor = groove::Editor(argv[1]); |
||||
else |
||||
editor = groove::Editor(); |
||||
groove::ncurses::ncurses curses; |
||||
curses.init(); |
||||
curses.flush(); |
||||
|
||||
while(editor.mode() != groove::Mode::QUIT) |
||||
{ |
||||
editor.render(); |
||||
int input = getch(); // Blocking until input
|
||||
editor.input(input); |
||||
} |
||||
|
||||
curses.quit(); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,9 @@ |
||||
//
|
||||
// Created by miko on 04.03.17.
|
||||
//
|
||||
|
||||
#include <Buffer.hpp> |
||||
|
||||
namespace groove { |
||||
|
||||
} |
@ -0,0 +1,53 @@ |
||||
#pragma once |
||||
|
||||
#include <vector> |
||||
#include <string> |
||||
#include <memory> |
||||
|
||||
namespace groove { |
||||
|
||||
class Buffer { |
||||
private: |
||||
std::vector<std::string> lines; |
||||
void removeTabs(std::string& line) { |
||||
ulong tab = line.find("\t"); |
||||
if(tab != line.npos) |
||||
this->removeTabs(line.replace(tab, 1, " ")); |
||||
} |
||||
public: |
||||
Buffer() : lines(std::vector<std::string>()) {} |
||||
std::vector<std::string>& linebuffer() { |
||||
return this->lines; |
||||
} |
||||
|
||||
void insert(std::string str, int line) { |
||||
this->lines.insert(this->lines.begin() + line, str); |
||||
this->removeTabs(this->lines.at(line)); |
||||
} |
||||
|
||||
void insert(std::string line) { |
||||
this->removeTabs(line); |
||||
this->lines.emplace_back(line); |
||||
} |
||||
void insert(int after) { |
||||
this->lines.insert(this->lines.begin() + after + 1, ""); |
||||
} |
||||
|
||||
void remove(int line) { |
||||
this->lines.erase(this->lines.begin() + line); |
||||
} |
||||
|
||||
void remove(int line, int car) { |
||||
this->lines.at(line).erase(this->lines.at(line).begin() + car); |
||||
} |
||||
|
||||
void deleteChar(int line, int car) { |
||||
this->lines.at(line).erase(this->lines.at(line).begin() + car); |
||||
} |
||||
|
||||
std::string& at(int line) { |
||||
return this->lines.at(line); |
||||
} |
||||
}; |
||||
|
||||
} |
@ -0,0 +1,234 @@ |
||||
#include <Editor.hpp> |
||||
#include <fstream> |
||||
#include <iostream> |
||||
|
||||
namespace groove { |
||||
|
||||
Editor::Editor(std::string filename) : x(0), y(0), buffer(std::make_unique<Buffer>()), |
||||
mode_(Mode::EDIT), filename(filename), offset(0) { |
||||
|
||||
if (!this->load()) { |
||||
std::cerr << "Could not open file: '" << this->filename << "', creating it on save.\n"; |
||||
this->buffer->insert(""); |
||||
} |
||||
} |
||||
|
||||
bool Editor::load() { |
||||
std::ifstream file(this->filename.c_str()); |
||||
if(file.is_open()) { |
||||
while(!file.eof()) { |
||||
std::string line; |
||||
std::getline(file, line); |
||||
this->buffer->insert(line); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
bool Editor::save() { |
||||
std::ofstream file(this->filename.c_str()); |
||||
if(file.is_open()) { |
||||
for (auto& line : this->buffer->linebuffer()) { |
||||
file << line << std::endl; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
void groove::Editor::input(int car) { |
||||
if (this->mode_ != Mode::SAVE) { |
||||
switch (car) { |
||||
case KEY_LEFT: |
||||
this->left(); |
||||
return; |
||||
case KEY_RIGHT: |
||||
this->right(); |
||||
return; |
||||
case KEY_UP: |
||||
this->up(); |
||||
return; |
||||
case KEY_DOWN: |
||||
this->down(); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
switch (this->mode_) { |
||||
case Mode::EDIT: |
||||
switch (car) { |
||||
case 'q': |
||||
this->mode_ = Mode::QUIT; |
||||
break; |
||||
case 'i': |
||||
this->mode_ = Mode::INSERT; |
||||
break; |
||||
case 'a': |
||||
this->x = static_cast<int>(this->buffer->at(this->y).length()); |
||||
this->mode_ = Mode::INSERT; |
||||
break; |
||||
case 's': |
||||
this->mode_ = Mode::SAVE; |
||||
break; |
||||
case 'c': |
||||
this->clipboard = this->buffer->at(this->y); |
||||
break; |
||||
case 'x': |
||||
this->clipboard = this->buffer->at(this->y); |
||||
this->buffer->remove(this->y); |
||||
break; |
||||
case 'p': |
||||
this->buffer->insert(this->clipboard, this->y); |
||||
this->y++; |
||||
this->x = this->buffer->at(this->y).size(); |
||||
break; |
||||
case 'd': |
||||
this->buffer->remove(this->y); |
||||
break; |
||||
} |
||||
break; |
||||
case Mode::INSERT: |
||||
switch (car) { |
||||
case 27: |
||||
this->mode_ = Mode::EDIT; |
||||
break; |
||||
case KEY_ENTER: |
||||
case 10: |
||||
{ |
||||
std::string appendix; |
||||
appendix = ""; |
||||
if (this->buffer->at(this->y).size() > this->x) { |
||||
appendix = this->buffer->at(this->y).substr(this->x, this->buffer->at(this->y).size()); |
||||
this->buffer->at(this->y) = this->buffer->at(this->y).substr(0, this->x); |
||||
} |
||||
this->buffer->insert(this->y); |
||||
this->y++; |
||||
this->buffer->linebuffer().at(this->y) += appendix; |
||||
this->x = 0; |
||||
} |
||||
break; |
||||
case KEY_BACKSPACE: |
||||
if (this->x - 1 >= 0) { |
||||
this->x--; |
||||
this->buffer->remove(this->y, this->x); |
||||
} |
||||
else { |
||||
if (this->buffer->at(this->y).size() > 0 && this->y > 0) { |
||||
int oldLength = static_cast<int>(this->buffer->at(this->y - 1).size()); |
||||
this->buffer->at(this->y - 1) += this->buffer->at(this->y); |
||||
this->buffer->remove(this->y); |
||||
this->y--; |
||||
this->x = oldLength; |
||||
} |
||||
else { |
||||
if (this->y - 1 >= 0) { |
||||
this->buffer->remove(this->y); |
||||
this->y--; |
||||
this->x = static_cast<int>(this->buffer->at(this->y).length()); |
||||
} |
||||
} |
||||
} |
||||
break; |
||||
case KEY_DC: |
||||
if (this->buffer->at(this->y).length() > this->x) |
||||
this->buffer->deleteChar(this->y, this->x); |
||||
else { |
||||
if (this->buffer->at(this->y).empty() && this->buffer->linebuffer().size() - 1 > this->y) |
||||
this->buffer->remove(this->y); |
||||
else if (this->buffer->linebuffer().size() - 1 > this->y) { |
||||
this->buffer->at(this->y) += this->buffer->at(this->y + 1); |
||||
this->buffer->remove(this->y + 1); |
||||
} |
||||
} |
||||
break; |
||||
case KEY_BTAB: |
||||
case KEY_CTAB: |
||||
case KEY_STAB: |
||||
case KEY_CATAB: |
||||
case 9: |
||||
this->buffer->at(this->y).insert(this->x, 4, ' '); |
||||
this->x += 4; |
||||
break; |
||||
default: |
||||
{ |
||||
std::ofstream log("log"); |
||||
log << car << std::endl; |
||||
this->buffer->at(this->y).insert(this->x, 1, char(car)); |
||||
if (this->lastChar != 195 && this->lastChar != 182) |
||||
this->x++; |
||||
this->lastChar = char(car); |
||||
} |
||||
break; |
||||
} |
||||
break; |
||||
case Mode::QUIT: |
||||
break; |
||||
case Mode::SAVE: |
||||
switch (car) { |
||||
case 'n': |
||||
case 27: |
||||
this->mode_ = Mode::EDIT; |
||||
break; |
||||
case 'y': |
||||
if (!this->save()) |
||||
std::cerr << "Could not write to file: '" << this->filename << "'\n"; |
||||
this->mode_ = Mode::EDIT; |
||||
break; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void Editor::render() { |
||||
clear(); |
||||
for (int i = 0; i < LINES - 1 + this->offset; i++) { |
||||
if(i >= this->buffer->linebuffer().size()) { |
||||
move(i - this->offset, 0); |
||||
clrtoeol(); |
||||
} |
||||
else { |
||||
std::string line = /*std::to_string(i) + " | " +*/ this->buffer->at(i); |
||||
mvprintw(i - this->offset, 0, line.c_str()); |
||||
} |
||||
clrtoeol(); |
||||
} |
||||
this->status(); |
||||
move(this->y - this->offset, this->x); |
||||
} |
||||
|
||||
void Editor::status() { |
||||
std::string status; |
||||
switch(this->mode_) |
||||
{ |
||||
case Mode::EDIT: |
||||
status = "[E]"; |
||||
break; |
||||
case Mode::INSERT: |
||||
status = "[I]"; |
||||
break; |
||||
case Mode::QUIT: |
||||
status = "[QUIT]"; |
||||
break; |
||||
} |
||||
status += "\t\t" + |
||||
std::to_string(this->y) + ", " + |
||||
std::to_string(this->x) + |
||||
"\t\t\tbuffer: " + std::to_string(this->buffer->linebuffer().size()) + |
||||
"\toffset: " + std::to_string(this->offset); |
||||
|
||||
if (this->mode_ == Mode::SAVE) |
||||
status = "Save File '" + this->filename + "'? [Y/N]: "; |
||||
|
||||
status += std::string(COLS - status.length(), ' '); |
||||
|
||||
attron(COLOR_PAIR(2)); |
||||
mvprintw(LINES-1, 0, status.c_str()); |
||||
|
||||
//clrtoeol();
|
||||
attroff(COLOR_PAIR(2)); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,82 @@ |
||||
#pragma once |
||||
|
||||
#include <memory> |
||||
#include <Buffer.hpp> |
||||
#include <ncurses.h> |
||||
|
||||
namespace groove { |
||||
|
||||
enum class Mode { |
||||
INSERT, |
||||
EDIT, |
||||
SAVE, |
||||
QUIT |
||||
}; |
||||
|
||||
class Editor { |
||||
private: |
||||
int x, y; |
||||
int offset; |
||||
char lastChar = 0; |
||||
std::unique_ptr<Buffer> buffer; |
||||
Mode mode_; |
||||
std::string clipboard = ""; |
||||
std::string filename; |
||||
|
||||
bool load(); |
||||
bool save(); |
||||
void status(); |
||||
void scrollUp() { |
||||
if (this->y < this->offset && this->offset > 0) |
||||
this->offset--; |
||||
} |
||||
void scrollDown() { |
||||
if (this->y >= LINES - 1) |
||||
this->offset++; |
||||
} |
||||
void left() { |
||||
if (this->x - 1 >= 0) |
||||
this->x--; |
||||
} |
||||
void right() { |
||||
if (this->x + 1 <= COLS && this->x + 1 <= this->buffer->linebuffer().at(this->y).length()) |
||||
this->x++; |
||||
} |
||||
void up() { |
||||
if (this->y - 1 >= 0) |
||||
this->y--; |
||||
else { |
||||
return; |
||||
} |
||||
if (this->x >= this->buffer->linebuffer().at(this->y).length()) { |
||||
int length = this->buffer->linebuffer().at(this->y).length(); |
||||
this->x = length > 0 ? length - 1 : 0; |
||||
} |
||||
this->scrollUp(); |
||||
} |
||||
void down() { |
||||
if (this->y + 1 < this->buffer->linebuffer().size()) |
||||
this->y++; |
||||
else { |
||||
return; |
||||
} |
||||
if (this->x >= this->buffer->linebuffer().at(this->y).length()) { |
||||
int length = this->buffer->linebuffer().at(this->y).length(); |
||||
this->x = length > 0 ? length - 1 : 0; |
||||
} |
||||
this->scrollDown(); |
||||
} |
||||
public: |
||||
Editor() : x(0), y(0), buffer(std::make_unique<Buffer>()), mode_(Mode::EDIT), filename("unbenannt"), |
||||
offset(0) { |
||||
this->buffer->insert(""); |
||||
} |
||||
Editor(std::string file); |
||||
Mode mode() { |
||||
return this->mode_; |
||||
} |
||||
void input(int c); |
||||
void render(); |
||||
}; |
||||
|
||||
} |
@ -0,0 +1,49 @@ |
||||
#pragma once |
||||
|
||||
#include <ncurses.h> |
||||
|
||||
namespace groove { |
||||
namespace ncurses { |
||||
|
||||
struct Color { |
||||
short r, g, b; |
||||
Color(short r, short g, short b) : |
||||
r(static_cast<short>(r * 0.255)), |
||||
g(static_cast<short>(g * 0.255)), |
||||
b(static_cast<short>(b * 0.255)) {} |
||||
}; |
||||
|
||||
class ncurses { |
||||
public: |
||||
ncurses() {}; |
||||
void init() { |
||||
setlocale(LC_ALL, ""); |
||||
initscr(); // Start ncurses mode
|
||||
noecho(); // Don't echo keystrokes
|
||||
cbreak(); // Disable line buffering
|
||||
keypad(stdscr, true); // Enable special keys to be recorde
|
||||
|
||||
start_color(); |
||||
init_pair(1, COLOR_WHITE, COLOR_BLACK); |
||||
init_pair(2, COLOR_WHITE, COLOR_MAGENTA); |
||||
if (can_change_color()) { |
||||
Color grey(39, 40, 34); |
||||
Color pink(255, 0, 103); |
||||
|
||||
init_color(COLOR_BLACK, grey.r, grey.g, grey.b); |
||||
init_color(COLOR_MAGENTA, pink.r, pink.g, pink.b); |
||||
} |
||||
attron(COLOR_PAIR(1)); |
||||
} |
||||
|
||||
void flush() { |
||||
refresh(); |
||||
} |
||||
|
||||
void quit() { |
||||
endwin(); |
||||
} |
||||
}; |
||||
|
||||
} |
||||
} |
Loading…
Reference in new issue