Compare commits

...

No commits in common. '2ec4f90bee7b23d253e2d5dc98dac2835bcb7af4' and '154289558a6e465d2d10d42871ad472664230742' have entirely different histories.

  1. 2
      .gitignore
  2. 14
      CHANGELOG.md
  3. 28
      CMakeLists.txt
  4. 19
      LICENSE
  5. 21
      LICENSE.md
  6. 4
      README.md
  7. 53
      main.cpp
  8. 9
      src/Buffer.cpp
  9. 77
      src/Buffer.hpp
  10. 173
      src/Editor.cpp
  11. 157
      src/Editor.hpp
  12. 63
      src/Highlighter.cpp
  13. 32
      src/Highlighter.hpp
  14. 52
      src/History.hpp
  15. 72
      src/config/ConfigParser.cpp
  16. 81
      src/config/ConfigParser.hpp
  17. 138
      src/modes/Edit.cpp
  18. 18
      src/modes/Edit.hpp
  19. 34
      src/modes/Exit.cpp
  20. 18
      src/modes/Exit.hpp
  21. 94
      src/modes/Insert.cpp
  22. 18
      src/modes/Insert.hpp
  23. 9
      src/modes/Mode.cpp
  24. 22
      src/modes/Mode.hpp
  25. 12
      src/modes/Quit.cpp
  26. 18
      src/modes/Quit.hpp
  27. 29
      src/modes/Save.cpp
  28. 16
      src/modes/Save.hpp
  29. 70
      src/modes/Search.cpp
  30. 24
      src/modes/Search.hpp
  31. 88
      src/ncurses/ncurses.hpp

2
.gitignore vendored

@ -0,0 +1,2 @@
.idea
build

@ -0,0 +1,14 @@
# v0.0.4
Major improvements:
* Undo/redo capability in `edit` mode
* Linemodes: `numbers`, `relativenumbers`, `nonumbers`
# v0.0.1
Simple editor working. The editor is capable of
* Loading files
* Saving files
* Editing files
* Cut, copy and pasting lines
* Scrolling

@ -0,0 +1,28 @@
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)
install(TARGETS groove DESTINATION /usr/bin)

@ -1,19 +0,0 @@
MIT License Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,21 @@
# The MIT License (MIT)
Copyright (c) 2017 Michael Ochmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -1,3 +1,3 @@
# groove # Groove Editor
– a simple `ncurses`-based texteditor for the terminaly
a simple ncurses-based texteditor for the terminal

@ -0,0 +1,53 @@
/**
* Groove Editor
*
* Version 0.0.4
*
* Copyright (c) 2017 Michael Ochmann
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <iostream>
#include <ncurses/ncurses.hpp>
#include <Editor.hpp>
#include <Highlighter.hpp>
int main(int argc, char* argv[]) {
std::unique_ptr<groove::Editor> editor;
if (argc > 1)
editor = std::make_unique<groove::Editor>(argv[1]);
else
editor = std::make_unique<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,77 @@
#pragma once
#include <vector>
#include <string>
#include <memory>
#include <stack>
namespace groove {
class Buffer {
private:
std::vector<std::string> lines;
std::vector<std::string> reverted;
std::stack<std::vector<std::string>> history;
void removeTabs(std::string& line) {
ulong tab = line.find("\t");
if(tab != line.npos)
this->removeTabs(line.replace(tab, 1, " "));
}
public:
bool changed;
Buffer() : lines(std::vector<std::string>()), reverted(std::vector<std::string>()), changed(false) {}
std::vector<std::string>& linebuffer() {
return this->lines;
}
void insert(std::string str, unsigned long line) {
this->lines.insert(this->lines.begin() + line, str);
this->removeTabs(this->lines.at(line));
this->changed = true;
}
void insert(std::string line) {
this->removeTabs(line);
this->lines.emplace_back(line);
this->changed = true;
}
void insert(unsigned long after) {
this->lines.insert(this->lines.begin() + after + 1, "");
this->changed = true;
}
void remove(unsigned long line) {
this->lines.erase(this->lines.begin() + line);
this->changed = true;
}
void remove(unsigned long line, int car) {
this->lines.at(line).erase(this->lines.at(line).begin() + car);
this->changed = true;
}
void deleteChar(unsigned long line, int car) {
this->lines.at(line).erase(this->lines.at(line).begin() + car);
this->changed = true;
}
std::string& at(unsigned long line) {
return this->lines.at(line);
}
void swap(std::vector<std::string> buffer) {
this->history.push(this->lines);
this->lines = buffer;
}
void revert() {
this->lines = this->history.top();
this->history.pop();
}
unsigned long size() {
return this->lines.size();
}
};
}

@ -0,0 +1,173 @@
#include <Editor.hpp>
#include <Highlighter.hpp>
#include <fstream>
#include <math.h>
namespace groove {
Editor::Editor(std::string filename) : x(0), y(0),
buffer(std::make_unique<Buffer>()), mode_(Mode::EDIT),
filename(filename), offset(0), voffset(0),
history(History()), inComment(false) {
std::string ending;
if (filename.find_last_of('.') != std::string::npos)
ending = std::string(filename.begin() + filename.find_last_of('.'), filename.end());
else
ending = "";
if (ending == ".cpp" || ending == ".hpp" || ending == ".h" || ending == ".c")
groove::Highlighter::list = groove::Highlighter::Syntaxes.at(groove::Syntax::CPP);
else
groove::Highlighter::list = groove::Highlighter::Syntaxes.at(groove::Syntax::DEFAULT);
this->modes.emplace(Mode::INSERT, std::make_unique<modes::Insert>(*this));
this->modes.emplace(Mode::EDIT, std::make_unique<modes::Edit>(*this));
this->modes.emplace(Mode::QUIT, std::make_unique<modes::Quit>(*this));
this->modes.emplace(Mode::SAVE, std::make_unique<modes::Save>(*this));
this->modes.emplace(Mode::EXIT, std::make_unique<modes::Exit>(*this));
this->modes.emplace(Mode::SEARCH, std::make_unique<modes::Search>(*this));
this->lineMode = this->config.get<std::string>("linenumbers") == "relative" ? LineMode::RELATIVE :
(this->config.get<std::string>("linenumbers") == "none" ? LineMode::NONE : LineMode::NUMBERS);
this->overlay = newwin(LINES * 0.9, COLS * 0.9, (LINES * 0.1f) / 2.0f, (COLS * 0.1f) / 2.0f);
box(this->overlay, 0, 0);
if (!this->load()) {
std::cerr << "Could not open file: '" << this->filename << "', creating it on save.\n";
this->buffer->insert("");
}
}
bool Editor::load() {
if (this->filename == "") {
this->filename = "unbenannt";
return false;
}
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);
}
this->buffer->changed = false;
return true;
}
this->buffer->changed = false;
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 c) {
this->modes.at(this->mode_)->input(c);
}
void Editor::render() {
long linenumber = this->offset;
this->vspace = this->lineMode == LineMode::RELATIVE || this->lineMode == LineMode::NUMBERS ?
Editor::Digits(this->buffer->linebuffer().size()) + 2 : 0;
clear();
for (int i = this->offset; i < LINES - 1 + this->offset; i++) {
long ln;
switch (this->lineMode) {
case LineMode::RELATIVE:
ln = static_cast<long>(std::sqrt(std::pow(static_cast<double>(this->y - linenumber), 2)));
ln = ln == 0 ? linenumber : ln;
break;
case LineMode::NUMBERS:
ln = linenumber;
break;
}
if(i >= this->buffer->linebuffer().size()) {
move(i - this->offset, 0);
clrtoeol();
}
else {
std::string line = this->buffer->at(i);
Highlighter highlighter(line);
std::unordered_map<long, std::pair<long, ncurses::Colors>> hilist = highlighter.get();
long x = this->vspace;
long found = -1;
long len = -1;
if (this->lineMode != LineMode::NONE) {
std::string label = std::string(x - Editor::Digits(ln) - 1, ' ');
label += std::to_string(ln) + ' ';
if (linenumber != this->y)
attron(COLOR_PAIR(ncurses::Colors::LINENUMBERS));
mvprintw(i - offset, 0, label.c_str());
if (linenumber != this->y)
attroff(COLOR_PAIR(ncurses::Colors::LINENUMBERS));
}
for (auto& car : line) {
if (car == '*' && this->lastChar == '/')
this->inComment = true;
if (car == '/' && this->lastChar == '*')
this->inComment = false;
if (hilist.find(x - this->vspace) != hilist.end()) {
found = x - this->vspace;
len = hilist.at(x - this->vspace).first;
}
if (found >= 0 && len >= 0) {
if (x - this->vspace >= found && x - this->vspace <= found + len - 1)
attron(COLOR_PAIR(hilist.at(found).second));
else if (x - this->vspace > found && x - this->vspace >= found + len) {
attron(COLOR_PAIR(ncurses::Colors::MAIN));
found = -1;
len = -1;
}
}
if (inComment)
attron(COLOR_PAIR(ncurses::Colors::COMMENTS));
mvaddch(i - this->offset, x , car);
attron(COLOR_PAIR(ncurses::Colors::MAIN));
this->lastChar = car;
x++;
}
//mvprintw(i - this->offset, 0, line.c_str());
}
clrtoeol();
linenumber++;
}
this->status();
move(static_cast<int>(this->y - this->offset), static_cast<int>(this->x + this->vspace));
this->renderOverlay();
}
void Editor::status() {
std::string status;
std::string position = std::to_string(this->y) + ", " + std::to_string(this->x) + " ";
status = this->modes.at(this->mode_)->status();
status += std::string(COLS - status.length() - position.length(), ' ');
status += position;
attron(COLOR_PAIR(ncurses::Colors::STATUSBAR));
mvprintw(LINES-1, 0, status.c_str());
attroff(COLOR_PAIR(ncurses::Colors::STATUSBAR));
}
long Editor::Digits(long number) {
return number > 0 ? static_cast<long>(log10 ((double) number) + 1) : 1;
}
}

@ -0,0 +1,157 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <Buffer.hpp>
#include <ncurses/ncurses.hpp>
#include <modes/Insert.hpp>
#include <modes/Edit.hpp>
#include <modes/Quit.hpp>
#include <modes/Save.hpp>
#include <modes/Search.hpp>
#include <History.hpp>
#include <config/ConfigParser.hpp>
#include <modes/Exit.hpp>
namespace groove {
enum class Mode {
INSERT,
EDIT,
SAVE,
QUIT,
EXIT,
SEARCH
};
enum LineMode {
NUMBERS,
RELATIVE,
NONE,
COUNT
};
class Editor {
friend class modes::Mode;
friend class modes::Insert;
friend class modes::Edit;
friend class modes::Quit;
friend class modes::Save;
friend class modes::Exit;
friend class modes::Search;
friend class History;
private:
long x, y;
int offset;
int voffset;
long vspace;
bool inComment;
char lastChar = 0;
WINDOW* overlay;
History history;
LineMode lineMode;
std::unique_ptr<Buffer> buffer;
Mode mode_;
std::string clipboard = "";
std::string filename;
std::unordered_map<Mode, std::unique_ptr<modes::Mode>> modes;
config::ConfigParser config;
bool load();
bool save();
void status();
void scrollUp() {
if (this->y < this->offset && this->offset > 0)
this->offset--;
}
void scrollDown() {
if (this->y - this->offset >= LINES - 1 && this->y < this->buffer->size())
this->offset++;
}
void left() {
if (this->x - 1 >= 0)
this->x--;
else {
if (this->y - 1 >= 0) {
this->y--;
this->x = this->buffer->at(this->y).length();
}
}
}
void right() {
if (this->x + 1 <= this->buffer->linebuffer().at(this->y).length()) {
if (this->x + 1 <= COLS)
this->x++;
else
this->voffset++;
}
else {
if (this->y + 1 < this->buffer->size()) {
this->y++;
this->x = this->buffer->at(this->y).size();
}
}
}
void up() {
if (this->y - 1 >= 0)
this->y--;
else {
return;
}
if (this->x >= this->buffer->linebuffer().at(this->y).length()) {
long 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()) {
long length = this->buffer->linebuffer().at(this->y).length();
this->x = length > 0 ? length - 1 : 0;
}
this->scrollDown();
}
bool movement(int c) {
switch (c) {
case KEY_LEFT:
this->left();
return true;
case KEY_RIGHT:
this->right();
return true;
case KEY_UP:
this->up();
return true;
case KEY_DOWN:
this->down();
return true;
default:
return false;
}
}
void renderOverlay() {
wbkgd(this->overlay, COLOR_PAIR(ncurses::Colors::STATUSBAR));
mvwaddstr(this->overlay, 3, 3, "Hallo win");
refresh();
wrefresh(this->overlay);
}
public:
Editor(std::string file = "ubenannt");
Mode mode() {
return this->mode_;
}
~Editor() {
delete this->overlay;
}
void input(int c);
void render();
static long Digits(long number);
};
}

@ -0,0 +1,63 @@
#include <regex>
#include <Highlighter.hpp>
#include <iostream>
namespace groove {
std::unordered_map<Syntax, RulesList> Highlighter::Syntaxes = {
std::make_pair(Syntax::CPP,
RulesList {
make_pair("([+-.<>,;=!:&*])", ncurses::Colors::CYAN),
make_pair("([\\{\\}\\[\\]\\(\\)])", ncurses::Colors::GREEN),
make_pair("(^|\\s)(while|if|try|catch|void|this|else|using|namespace|private|public|protected|friend|class|char|bool|unsigned|long|short|int|return):?(\\s+|$)\\*?", ncurses::Colors::MAGENTA),
make_pair("([a-zA-Z_][a-zA-Z_0-9]+)::", ncurses::Colors::GREEN),
make_pair("::([a-zA-Z_][a-zA-Z_0-9]+)", ncurses::Colors::CYAN),
make_pair("\\.([a-zA-Z_][a-zA-Z_0-9]+)", ncurses::Colors::CYAN),
make_pair("([_a-zA-Z][a-zA-Z0-9_-]+)\\(", ncurses::Colors::CYAN),
make_pair("\\\".*\\\"", ncurses::Colors::ORANGE),
make_pair("/\\*.*\\*//*", ncurses::Colors::COMMENTS),
make_pair("(//.*)", ncurses::Colors::ORANGE)
}),
std::make_pair(Syntax::DEFAULT,
RulesList {
make_pair("([.,;!\\*])", ncurses::Colors::MAGENTA),
make_pair("([\\{\\}\\[\\]\\(\\)])", ncurses::Colors::GREEN),
make_pair("\\\".*\\\"", ncurses::Colors::ORANGE),
make_pair("/\\*.*\\*//*", ncurses::Colors::COMMENTS),
})
};
RulesList Highlighter::list = RulesList();
std::unordered_map<long, std::pair<long, ncurses::Colors>> groove::Highlighter::get() {
std::unordered_map<long, std::pair<long, ncurses::Colors>> list;
if (this->line.size() > 0 && this->line.at(0) == '#') {
list.emplace(0, std::make_pair(this->line.size(), ncurses::Colors::COMMENTS));
return list;
}
for (auto &keyword : Highlighter::list) {
try {
std::sregex_iterator next(this->line.begin(), this->line.end(), keyword.first);
std::sregex_iterator end;
while (next != end) {
std::smatch match = *next;
for (unsigned i = 0; i < match.size(); ++i) {
list.emplace(match.position(i), std::make_pair(match.length(i), keyword.second));
}
next++;
}
} catch (std::regex_error &e) {
// Syntax error in the regular expression
}
}
return list;
}
}

@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <ncurses/ncurses.hpp>
#include <vector>
#include <unordered_map>
#include <regex>
namespace groove {
typedef std::vector<std::pair<std::regex, ncurses::Colors>> RulesList;
inline std::pair<std::regex, ncurses::Colors> make_pair(std::string regex, ncurses::Colors color) {
return std::make_pair(std::regex(regex), color);
};
enum class Syntax {
DEFAULT,
CPP
};
class Highlighter {
private:
std::string line;
public:
static std::unordered_map<Syntax, RulesList> Syntaxes;
static RulesList list;
Highlighter(std::string line) : line(line) {}
std::unordered_map<long, std::pair<long, ncurses::Colors>> get();
};
}

@ -0,0 +1,52 @@
#pragma once
#include <functional>
#include <memory>
#include <vector>
namespace groove {
typedef std::function<void()> lambda;
struct HistoryNode {
lambda undo, redo;
HistoryNode(lambda undo, lambda redo) :
undo(std::move(undo)), redo(std::move(redo)) {}
};
class History {
private:
std::unique_ptr<std::vector<HistoryNode>> history;
unsigned long pointer;
void cutTail() {
if (this->history->begin() == this->history->end() || this->pointer >= this->history->size())
return;
this->history->erase(this->history->begin() + this->pointer + 1, this->history->end());
}
public:
History() : history(std::make_unique<std::vector<HistoryNode>>()), pointer(0) {
this->push(HistoryNode([](){}, [](){}));
}
void push(HistoryNode node) {
this->cutTail();
this->history->emplace_back(std::move(node));
this->pointer = this->history->size() - 1;
}
void undo() {
if (this->pointer <= 0)
return;
this->history->at(this->pointer).undo();
this->pointer--;
}
void redo() {
if (this->pointer + 1 >= this->history->size())
return;
this->history->at(this->pointer + 1).redo();
this->pointer++;
}
};
}

@ -0,0 +1,72 @@
#include <config/ConfigParser.hpp>
#include <fstream>
namespace groove {
namespace config {
void ConfigValue::set(std::string &value) {
strVal = value;
boolVal = value == "true";
try {
intVal = std::stoi(value);
} catch (std::invalid_argument) {
intVal = 0;
}
try {
floatVal = std::stof(value);
} catch (std::invalid_argument) {
floatVal = 0.0f;
}
}
void ConfigParser::parse() {
std::ifstream file(this->filename.c_str());
if(file.is_open()) {
while(!file.eof()) {
std::string line;
std::getline(file, line);
if (line == "" || line.at(0) == '#')
continue;
std::vector<std::string> pair = std::explode(line,'=');
std::string key = pair.at(0);
std::string value = pair.at(1);
std::trim(key);
std::trim(value);
this->params->emplace(key, ConfigValue(value));
}
}
else {
std::cerr << "Could not load config file." << std::endl;
}
}
template<>
std::string ConfigParser::get<std::string>(const std::string& key) {
if (this->params->find(key) == this->params->end())
return "";
return this->params->at(key).strVal;
}
template<>
int ConfigParser::get<int>(const std::string& key) {
if (this->params->find(key) == this->params->end())
return 0;
return this->params->at(key).intVal;
}
template<>
float ConfigParser::get<float>(const std::string& key) {
if (this->params->find(key) == this->params->end())
return 0.0f;
return this->params->at(key).floatVal;
}
template<>
bool ConfigParser::get<bool>(const std::string& key) {
if (this->params->find(key) == this->params->end())
return false;
return this->params->at(key).boolVal;
}
}
}

@ -0,0 +1,81 @@
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <algorithm>
#include <iostream>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <memory>
namespace std {
static inline std::vector<std::string> explode(const std::string &str, char delimiter) {
std::vector<std::string> tokens;
std::stringstream inStream(str);
std::string tempString;
while (std::getline(inStream, tempString, delimiter))
tokens.push_back(tempString);
return tokens;
}
static inline void ltrim(std::string &string) {
string.erase(string.begin(), std::find_if(string.begin(), string.end(), [](auto c){return !std::isspace(c);}));
}
static inline void rtrim(std::string &string) {
string.erase(std::find_if(string.rbegin(), string.rend(), [](auto c){return !std::isspace(c);}).base(), string.end());
}
static inline void trim(std::string &string) {
rtrim(string);
ltrim(string);
}
}
namespace groove {
namespace config {
struct ConfigValue {
std::string strVal;
int intVal;
float floatVal;
bool boolVal;
ConfigValue(std::string &value) {
set(value);
}
void set(std::string &value);
};
class ConfigParser {
private:
std::unique_ptr<std::unordered_map<std::string, ConfigValue>> params;
std::string filename;
void parse();
public:
ConfigParser() : params(std::make_unique<std::unordered_map<std::string, ConfigValue>>()) {
this->filename = getenv("HOME");
this->filename += "/.grooverc";
this->parse();
}
ConfigParser(const ConfigParser&) = delete;
template <typename T>
T get(const std::string& key);
template<typename T>
void set(std::string key, T value) {
std::string newValue = std::to_string(value);
this->params->at(key).set(newValue);
}
};
}
}

@ -0,0 +1,138 @@
#include <modes/Edit.hpp>
#include <Editor.hpp>
namespace groove {
namespace modes {
void Edit::input(int c) {
this->status_ = this->editor.buffer->changed ? "*" : "";
if (this->editor.movement(c))
return;
switch (c) {
case 'q':
this->editor.mode_ = groove::Mode::EXIT;
this->editor.input(' ');
break;
case 'i':
{
std::vector<std::string>& oldBuffer = this->editor.buffer->linebuffer();
this->editor.history.push(HistoryNode(
[this, oldBuffer](){
this->editor.buffer->swap(oldBuffer);
}, [this](){
this->editor.buffer->revert();
})
);
this->editor.mode_ = groove::Mode::INSERT;
}
break;
case 'a':
this->editor.x = static_cast<int>(this->editor.buffer->at(this->editor.y).length());
this->editor.mode_ = groove::Mode::INSERT;
break;
case 's':
this->editor.mode_ = groove::Mode::SAVE;
break;
case 'c':
this->editor.clipboard = this->editor.buffer->at(this->editor.y);
break;
case 'x':
{
long y = this->editor.y;
std::string line = this->editor.buffer->at(y);
this->editor.history.push(
HistoryNode([this, y, line](){
this->editor.buffer->insert(line, y);
}, [this, y](){
this->editor.buffer->remove(y);
})
);
this->editor.clipboard = this->editor.buffer->at(y);
this->editor.buffer->remove(y);
}
break;
case 'v':
{
std::string clipboard = this->editor.clipboard;
long y = this->editor.y;
this->editor.history.push(
HistoryNode([this, y](){
this->editor.buffer->remove(y);
},[this, y, clipboard](){
this->editor.buffer->insert(clipboard, y);
this->editor.y = y;
this->editor.x = this->editor.buffer->at(y).size();
})
);
this->editor.buffer->insert(clipboard, y);
this->editor.y++;
this->editor.x = this->editor.buffer->at(y).size();
}
break;
case 'd':
{
if (this->editor.y >= this->editor.buffer->size() - 1)
break;
long y = this->editor.y;
std::string line = this->editor.buffer->at(y);
this->editor.history.push(
HistoryNode([this, y, line](){
this->editor.buffer->insert(line, y);
}, [this, y, line](){
this->editor.buffer->remove(y);
})
);
this->editor.buffer->remove(this->editor.y);
}
break;
case 'l':
switch (this->editor.lineMode) {
case LineMode::NUMBERS:
this->editor.lineMode = LineMode::RELATIVE;
break;
case LineMode::RELATIVE:
this->editor.lineMode = LineMode::NONE;
break;
case LineMode::NONE:
this->editor.lineMode = LineMode::NUMBERS;
break;
}
break;
case 'f':
this->editor.mode_ = groove::Mode::SEARCH;
this->editor.input(' ');
break;
case 'u':
this->editor.history.undo();
break;
case 'r':
this->editor.history.redo();
break;
case '^':
this->editor.x = 0;
break;
case '$':
this->editor.x = this->editor.buffer->at(this->editor.y).size();
break;
case 'g':
this->editor.y = 0;
this->editor.offset = 0;
break;
case 'G':
this->editor.y = this->editor.buffer->size() - 1;
this->editor.offset = this->editor.buffer->size() - LINES + 1;
break;
case KEY_NPAGE:
for (int i = 0; i < LINES; i++) {
this->editor.down();
}
break;
case KEY_PPAGE:
for (int i = 0; i < LINES; i++) {
this->editor.up();
}
break;
}
}
}
}

@ -0,0 +1,18 @@
#pragma once
#include <modes/Mode.hpp>
namespace groove {
namespace modes {
class Edit : public Mode {
public:
using Mode::Mode;
std::string status() {
return this->status_;
}
void input(int c);
};
}
}

@ -0,0 +1,34 @@
#include <modes/Exit.hpp>
#include <Editor.hpp>
namespace groove {
namespace modes {
void Exit::input(int c) {
if (!this->editor.buffer->changed) {
this->editor.mode_ = groove::Mode::QUIT;
return;
}
this->status_ = "Save file '" + this->editor.filename + "'? [Y/N]:";
switch (c) {
case 27:
this->status_ = "";
this->editor.mode_ = groove::Mode::EDIT;
this->editor.input(' ');
break;
case 'n':
this->editor.mode_ = groove::Mode::QUIT;
break;
case 'y':
if (!this->editor.save())
std::cerr << "Could not write to file: '" << this->editor.filename << "'\n";
this->editor.mode_ = groove::Mode::QUIT;
break;
}
}
}
}

@ -0,0 +1,18 @@
#pragma once
#include <modes/Mode.hpp>
namespace groove {
namespace modes {
class Exit : public Mode {
public:
using Mode::Mode;
std::string status() {
return this->status_;
}
void input(int c);
};
}
}

@ -0,0 +1,94 @@
#include <modes/Insert.hpp>
#include <Editor.hpp>
#include <iostream>
namespace groove {
namespace modes {
void Insert::input(int c) {
if (this->editor.movement(c))
return;
switch (c) {
case 27:
this->editor.mode_ = groove::Mode::EDIT;
break;
case KEY_ENTER:
case 10:
{
std::string appendix;
appendix = "";
if (this->editor.buffer->at(this->editor.y).size() > this->editor.x) {
appendix = this->editor.buffer->at(this->editor.y).substr(this->editor.x, this->editor.buffer->at(this->editor.y).size());
this->editor.buffer->at(this->editor.y) = this->editor.buffer->at(this->editor.y).substr(0, this->editor.x);
}
this->editor.buffer->insert(this->editor.y);
this->editor.y++;
this->editor.buffer->linebuffer().at(this->editor.y) += appendix;
this->editor.x = 0;
this->editor.scrollDown();
}
break;
case KEY_BACKSPACE:
if (this->editor.x - 1 >= 0) {
this->editor.x--;
this->editor.buffer->remove(this->editor.y, this->editor.x);
}
else {
if (this->editor.buffer->at(this->editor.y).size() > 0 && this->editor.y > 0) {
int oldLength = static_cast<int>(this->editor.buffer->at(this->editor.y - 1).size());
this->editor.buffer->at(this->editor.y - 1) += this->editor.buffer->at(this->editor.y);
this->editor.buffer->remove(this->editor.y);
this->editor.y--;
this->editor.x = oldLength;
}
else {
if (this->editor.y - 1 >= 0) {
this->editor.buffer->remove(this->editor.y);
this->editor.y--;
this->editor.x = static_cast<int>(this->editor.buffer->at(this->editor.y).length());
}
}
}
this->editor.scrollUp();
break;
case KEY_DC:
if (this->editor.buffer->at(this->editor.y).length() > this->editor.x)
this->editor.buffer->deleteChar(this->editor.y, this->editor.x);
else {
if (this->editor.buffer->at(this->editor.y).empty() && this->editor.buffer->linebuffer().size() - 1 > this->editor.y)
this->editor.buffer->remove(this->editor.y);
else if (this->editor.buffer->linebuffer().size() - 1 > this->editor.y) {
this->editor.buffer->at(this->editor.y) += this->editor.buffer->at(this->editor.y + 1);
this->editor.buffer->remove(this->editor.y + 1);
}
}
break;
case KEY_BTAB:
case KEY_CTAB:
case KEY_STAB:
case KEY_CATAB:
case 9:
this->editor.buffer->at(this->editor.y).insert(this->editor.x, 4, ' ');
this->editor.x += 4;
break;
default:
{
switch (c) {
case '{':
case '[':
this->editor.buffer->at(this->editor.y).insert(this->editor.x, 1, c + 2);
break;
case '(':
this->editor.buffer->at(this->editor.y).insert(this->editor.x, 1, c + 1);
break;
}
this->editor.buffer->at(this->editor.y).insert(this->editor.x, 1, char(c));
if (this->editor.lastChar != 195 && this->editor.lastChar != 182)
this->editor.x++;
this->editor.lastChar = char(c);
}
break;
}
}
}
}

@ -0,0 +1,18 @@
#pragma once
#include <modes/Mode.hpp>
namespace groove {
namespace modes {
class Insert : public Mode {
public:
using Mode::Mode;
std::string status() {
return " -- INSERT --" + this->status_;
}
void input(int c);
};
}
}

@ -0,0 +1,9 @@
#include <modes/Mode.hpp>
namespace groove {
namespace modes {
Mode::~Mode() {}
}
}

@ -0,0 +1,22 @@
#pragma once
#include <string>
namespace groove {
class Editor;
namespace modes {
class Mode {
protected:
groove::Editor& editor;
std::string status_;
public:
Mode(groove::Editor& editor) : editor(editor) , status_("") {}
virtual ~Mode() = 0;
virtual std::string status() = 0;
virtual void input(int c) = 0;
};
}
}

@ -0,0 +1,12 @@
#include <modes/Quit.hpp>
namespace groove {
namespace modes {
void Quit::input(int c) {
}
}
}

@ -0,0 +1,18 @@
#pragma once
#include <modes/Mode.hpp>
namespace groove {
namespace modes {
class Quit : public Mode {
public:
using Mode::Mode;
std::string status() {
return this->status_;
}
void input(int c);
};
}
}

@ -0,0 +1,29 @@
#include <modes/Save.hpp>
#include <Editor.hpp>
#include <iostream>
namespace groove {
namespace modes {
void Save::input(int c) {
switch (c) {
case 'n':
case 27:
this->editor.mode_ = groove::Mode::EDIT;
break;
case 'y':
if (!this->editor.save())
std::cerr << "Could not write to file: '" << this->editor.filename << "'\n";
this->editor.buffer->changed = false;
this->editor.mode_ = groove::Mode::EDIT;
break;
}
}
std::string Save::status() {
this->status_ = "Save file '" + this->editor.filename + "'? [Y/N]:";
return this->status_;
}
}
}

@ -0,0 +1,16 @@
#pragma once
#include <modes/Mode.hpp>
namespace groove {
namespace modes {
class Save : public Mode {
public:
using Mode::Mode;
std::string status();
void input(int c);
};
}
}

@ -0,0 +1,70 @@
#include <modes/Search.hpp>
#include <Editor.hpp>
#include <regex>
namespace groove {
namespace modes {
void Search::input(int c) {
this->editor.movement(c);
switch (c) {
case 27:
this->editor.mode_ = groove::Mode::EDIT;
this->editor.input(' ');
break;
case KEY_BACKSPACE:
if (this->keyword.size() > 0)
this->keyword.pop_back();
break;
case KEY_ENTER:
case 10:
this->search();
break;
default:
{
std::regex regex("[ -~]");
std::string character(1, c);
if (!std::regex_match(character, regex))
break;
this->keyword += c;
}
break;
}
}
void Search::search() {
if (this->keyword == "")
return;
std::regex regex(this->keyword);
int pos = this->match == 0 ? this->position + 1 : this->position;
for (long i = pos; i < this->editor.buffer->size(); i++) {
if (i >= this->editor.buffer->size() - 1)
this->position = 0;
std::string line = this->editor.buffer->at(i);
try {
std::sregex_iterator next(line.begin(), line.end(), regex);
std::sregex_iterator end;
if (next == end) {
this->match = 0;
}
while (next != end) {
std::smatch match = *next;
for (unsigned n = this->match; n < match.size(); ++n) {
this->editor.y = i;
this->editor.offset = i - LINES / 2;
this->editor.x = match.position(n);
this->position = i;
this->match++;
return;
}
next++;
}
} catch (std::regex_error& e) {
// Syntax error in the regular expression
}
}
}
}
}

@ -0,0 +1,24 @@
#pragma once
#include <modes/Mode.hpp>
namespace groove {
namespace modes {
class Search : public Mode {
private:
std::string keyword;
long position;
int match;
void search();
public:
Search(groove::Editor& editor) : Mode(editor), keyword(""), position(0), match(0) {};
std::string status() {
return "\tFind: " + this->keyword + ' ' + this->status_;
}
void input(int c);
};
}
}

@ -0,0 +1,88 @@
#pragma once
#include <ncurses.h>
#include <iostream>
#define CURSES_MAX_COLOR 1000.0f
namespace groove {
namespace ncurses {
struct Color {
short r, g, b;
Color(short r, short g, short b) :
r(static_cast<short>(r / 255.0f * CURSES_MAX_COLOR)),
g(static_cast<short>(g / 255.0f * CURSES_MAX_COLOR)),
b(static_cast<short>(b / 255.0f * CURSES_MAX_COLOR)) {}
};
enum Colors {
MAIN = 1,
STATUSBAR,
MAGENTA,
GREEN,
PURPLE,
ORANGE,
CYAN,
GREY,
LINENUMBERS,
COMMENTS
};
class ncurses {
private:
WINDOW* overlay;
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();
if (can_change_color()) {
Color grey(39, 40, 34);
Color pink(255, 0, 103);
Color cyan(102 , 217, 239);
Color green(162, 217, 43);
Color lightgray(60, 61, 55);
Color lighterGray(144, 144, 138);
Color yellow(230, 219, 116);
init_color(COLOR_BLACK, grey.r, grey.g, grey.b);
init_color(COLOR_MAGENTA, pink.r, pink.g, pink.b);
init_color(COLOR_CYAN, cyan.r, cyan.g, cyan.b);
init_color(COLOR_BLUE, lightgray.r, lightgray.g, lightgray.b);
init_color(COLOR_YELLOW, yellow.r, yellow.g, yellow.b);
init_color(COLOR_GREEN, green.r, green.g, green.b);
init_color(COLOR_RED, lighterGray.r, lighterGray.g, lighterGray.b);
}
init_pair(Colors::LINENUMBERS, COLOR_WHITE, COLOR_BLUE);
init_pair(Colors::COMMENTS, COLOR_RED, COLOR_BLACK);
init_pair(Colors::MAIN, COLOR_WHITE, COLOR_BLACK);
init_pair(Colors::STATUSBAR, COLOR_WHITE, COLOR_MAGENTA);
init_pair(Colors::MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
init_pair(Colors::GREEN, COLOR_GREEN, COLOR_BLACK);
init_pair(Colors::GREY, COLOR_YELLOW, COLOR_BLACK);
init_pair(Colors::CYAN, COLOR_CYAN, COLOR_BLACK);
init_pair(Colors::ORANGE, COLOR_YELLOW, COLOR_BLACK);
attron(COLOR_PAIR(1));
}
void flush() {
refresh();
}
void quit() {
endwin();
}
};
}
}
Loading…
Cancel
Save