From 9a3378b3da88c91381c77a4bab7566849d7dfd12 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Wed, 8 Oct 2025 11:04:27 +1100 Subject: [PATCH] Undo, redo, line numbers --- README.md | 34 ++++++++++++++++ src/main.cpp | 110 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 124 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 22eee7e..8947db0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ ve is a vi(m) like editor which runs inside your terminal, using ncurses. At pre * Insert mode works in the way you'd expect * Scroll through large files * Saving and quitting in the same way as Vim +* Delimiter/action-based undo +* Line number counter ## Building @@ -15,4 +17,36 @@ ve is a vi(m) like editor which runs inside your terminal, using ncurses. At pre g++ src/main.cpp -Lncurses -o ve ``` +## Keybinds +### Normal Mode + +**Moving around:** Use `h` to go left, `j` to go up, `k` to go down, and `l` top go right (or arrow keys if you're boring). + +**Enter Insert mode:** Use `i` to enter insert mode + +**Enter Command mode:** Use `:` to enter command mode + +**Undo/Redo:** Press `u` to undo a mistake, and `r` to redo. + +### Insert Mode + +**Typing:** Type things in just like in any other editor. + +**Moving around:** Use the arrow keys + +**Enter Normal mode:** Press `ESC`. + +### Command Mode + +There are currently three commands for command mode: + +`q`: Quits the editor, unless you have unsaved changes. + +`q!`: Force quits the editor. + +`w`: Saves changes to the file. If a filename is specified, changes are saved to that file. + +`wq`: Saves changes to the file, and quits the editor. If a filename is specified, changes are saved to that file. + +Press enter to submit your command. diff --git a/src/main.cpp b/src/main.cpp index a9ebdf5..3ccd849 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include enum class mode { INSERT, COMMAND, NORMAL @@ -13,6 +14,27 @@ void quit(int exitcode = 0) { exit(exitcode); } +struct cursor { + int x; + int y; +}; + +struct UndoState { + std::vector lines; + cursor pos; + UndoState(std::vector inlines, cursor inpos) : lines(inlines), pos(inpos) {} +}; + +struct Configuration { + std::string tab = " "; + std::vector undoDelimiters = {'{', '}', ';', '(', ')', '\n', '='}; +}; + +Configuration conf; + +std::vector undoHistory; +int undos = -1; + std::vector readFile(std::string filename) { std::ifstream file(filename); if (!file) { @@ -67,6 +89,15 @@ int writeFile(std::string filename, std::vector content) { } } +std::string linenum(int in) { + in++; + std::string result = std::to_string(in); + while (result.size() < 5) { + result = " " + result; + } + return result; +} + int main(int argc, char** argv) { std::string filename; @@ -90,8 +121,9 @@ int main(int argc, char** argv) { keypad(stdscr, TRUE); // remember the position of the cursor - struct {int x = 0; int y = 1;} pos; + cursor pos; pos.x = lines[0].size(); + pos.y = 1; // set current mode mode currMode = mode::NORMAL; @@ -118,20 +150,23 @@ int main(int argc, char** argv) { // make sure we keep track of whether we've written to the file bool fileChanged = false; + // create initial undo history + undoHistory.push_back(UndoState(lines, pos)); + // initial rendering before doing anything // display the current mode at the bottom switch (currMode) { case mode::NORMAL: - mvprintw(windowSize.y, 0, "NORMAL"); + mvprintw(windowSize.y, 1, "normal"); break; case mode::COMMAND: - mvprintw(windowSize.y, 0, "COMMAND"); + mvprintw(windowSize.y, 1, "command"); break; case mode::INSERT: - mvprintw(windowSize.y, 0, "INSERT"); + mvprintw(windowSize.y, 1, "insert"); break; default: - mvprintw(windowSize.y, 0, "Unknown Mode! Press any key to exit"); + mvprintw(windowSize.y, 1, "Unknown Mode! Press any key to exit"); getch(); quit(); break; @@ -139,11 +174,12 @@ int main(int argc, char** argv) { // display what's in the buffer for (size_t i = viewpoint.top; i < lines.size() && i < viewpoint.bottom && (i - viewpoint.top) < windowSize.y; i++) { - mvprintw((i - viewpoint.top) + 1, 0, "%s", lines[i].c_str()); + mvprintw((i - viewpoint.top) + 1, 6, "%s", lines[i].c_str()); + mvprintw((i - viewpoint.top) + 1, 0, "%s", linenum(i).c_str()); } // go to where our position is - move(pos.y, pos.x); + move(pos.y, pos.x + 6); // put what's in the buffer on screen refresh(); @@ -165,13 +201,38 @@ int main(int argc, char** argv) { case ':': currMode = mode::COMMAND; break; + case 'u': + if (undos <= -1) { + undos = undoHistory.size() - 1; + } else { + undos --; + } + if (undos < 0) { + undos = 0; + statusMessage = "No changes to undo"; + statusCode = 0; + break; + } + lines = undoHistory[undos].lines; + pos = undoHistory[undos].pos; + break; + case 'r': + if (undos + 1 >= undoHistory.size()) { + statusMessage = "No changes to redo"; + statusCode = 0; + break; + } + undos ++; + lines = undoHistory[undos].lines; + pos = undoHistory[undos].pos; + break; case 'h': case KEY_LEFT: if (pos.x > 0) { pos.x--; } break; - case 'j': + case 'k': case KEY_DOWN: if (pos.y < lines.size()) { pos.y++; @@ -190,7 +251,7 @@ int main(int argc, char** argv) { pos.x++; } break; - case 'k': + case 'j': case KEY_UP: if (pos.y > 1) { pos.y--; @@ -215,6 +276,8 @@ int main(int argc, char** argv) { // escape has been pressed (code 27) case 27: currMode = mode::NORMAL; + undoHistory.push_back(UndoState(lines, pos)); + undos = -1; break; // backspace has been pressed // boy there are a lot of ways to say backspace @@ -310,6 +373,11 @@ int main(int argc, char** argv) { } pos.x++; if (!fileChanged) fileChanged = true; + + // Do undo + if (std::find(conf.undoDelimiters.begin(), conf.undoDelimiters.end(), static_cast(pressed)) != conf.undoDelimiters.end()) { + undoHistory.push_back(UndoState(lines, pos)); + } } } break; @@ -362,11 +430,11 @@ int main(int argc, char** argv) { if (commandbuf.substr(0, 2) == "wq") { if (filename.empty()) { - if (commandbuf.size() > 1) { - if (commandbuf[1] == ' ') { - filename = commandbuf.substr(2); + if (commandbuf.size() > 2) { + if (commandbuf[2] == ' ') { + filename = commandbuf.substr(3); } else { - filename = commandbuf.substr(1); + filename = commandbuf.substr(2); } writeFile(filename, lines); quit(); @@ -387,6 +455,7 @@ int main(int argc, char** argv) { default: // add to the command buffer commandbuf += pressed; + break; } } } @@ -394,25 +463,26 @@ int main(int argc, char** argv) { // erase our buffer ready to rerender erase(); - // display what's in the buffer + // display what's in the buffer as well as the line counter for (size_t i = viewpoint.top; i < lines.size() && i < viewpoint.bottom && (i - viewpoint.top) < windowSize.y; i++) { - mvprintw((i - viewpoint.top) + 1, 0, "%s", lines[i].c_str()); + mvprintw((i - viewpoint.top) + 1, 6, "%s", lines[i].c_str()); + mvprintw((i - viewpoint.top) + 1, 0, "%s", linenum(i).c_str()); } // display the current mode at the bottom switch (currMode) { case mode::NORMAL: - mvprintw(windowSize.y, 0, "NORMAL"); + mvprintw(windowSize.y, 1, "normal"); break; case mode::COMMAND: - mvprintw(windowSize.y, 0, "COMMAND"); + mvprintw(windowSize.y, 1, "command"); mvprintw(windowSize.y - 1, 0, (":" + commandbuf).c_str()); break; case mode::INSERT: - mvprintw(windowSize.y, 0, "INSERT"); + mvprintw(windowSize.y, 1, "insert"); break; default: - mvprintw(windowSize.y, 0, "Unknown Mode! Press any key to exit"); + mvprintw(windowSize.y, 1, "Unknown Mode! Press any key to exit"); getch(); quit(); break; @@ -426,7 +496,7 @@ int main(int argc, char** argv) { } // put the cursor in position - move(pos.y - viewpoint.top, pos.x); + move(pos.y - viewpoint.top, pos.x + 6); // put what's in the buffer on screen refresh();