commit b1db1bcc8a61b00bb201129313b144db44da681e Author: Michael Ochmann Date: Sun Mar 5 19:07:20 2017 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08fe6ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5a85fa1 --- /dev/null +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..71a702f --- /dev/null +++ b/main.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +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; +} \ No newline at end of file diff --git a/src/Buffer.cpp b/src/Buffer.cpp new file mode 100644 index 0000000..a5878ec --- /dev/null +++ b/src/Buffer.cpp @@ -0,0 +1,9 @@ +// +// Created by miko on 04.03.17. +// + +#include + +namespace groove { + +} diff --git a/src/Buffer.hpp b/src/Buffer.hpp new file mode 100644 index 0000000..faa63d1 --- /dev/null +++ b/src/Buffer.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +namespace groove { + + class Buffer { + private: + std::vector 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::vector& 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); + } + }; + +} \ No newline at end of file diff --git a/src/Editor.cpp b/src/Editor.cpp new file mode 100644 index 0000000..ea248b9 --- /dev/null +++ b/src/Editor.cpp @@ -0,0 +1,234 @@ +#include +#include +#include + +namespace groove { + + Editor::Editor(std::string filename) : x(0), y(0), buffer(std::make_unique()), + 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(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(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(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)); + } + +} \ No newline at end of file diff --git a/src/Editor.hpp b/src/Editor.hpp new file mode 100644 index 0000000..f1f243f --- /dev/null +++ b/src/Editor.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +namespace groove { + + enum class Mode { + INSERT, + EDIT, + SAVE, + QUIT + }; + + class Editor { + private: + int x, y; + int offset; + char lastChar = 0; + std::unique_ptr 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()), 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(); + }; + +} \ No newline at end of file diff --git a/src/ncurses/ncurses.hpp b/src/ncurses/ncurses.hpp new file mode 100644 index 0000000..0b7c407 --- /dev/null +++ b/src/ncurses/ncurses.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace groove { + namespace ncurses { + + struct Color { + short r, g, b; + Color(short r, short g, short b) : + r(static_cast(r * 0.255)), + g(static_cast(g * 0.255)), + b(static_cast(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(); + } + }; + + } +} \ No newline at end of file