Compare commits
No commits in common. '2ec4f90bee7b23d253e2d5dc98dac2835bcb7af4' and '154289558a6e465d2d10d42871ad472664230742' have entirely different histories.
2ec4f90bee
...
154289558a
31 changed files with 1444 additions and 21 deletions
@ -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…
Reference in new issue