Undo, redo, line numbers

This commit is contained in:
2025-10-08 11:04:27 +11:00
parent 5fa3e9e6a1
commit 9a3378b3da
2 changed files with 124 additions and 20 deletions

View File

@@ -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.

View File

@@ -3,6 +3,7 @@
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
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<std::string> lines;
cursor pos;
UndoState(std::vector<std::string> inlines, cursor inpos) : lines(inlines), pos(inpos) {}
};
struct Configuration {
std::string tab = " ";
std::vector<char> undoDelimiters = {'{', '}', ';', '(', ')', '\n', '='};
};
Configuration conf;
std::vector<UndoState> undoHistory;
int undos = -1;
std::vector<std::string> readFile(std::string filename) {
std::ifstream file(filename);
if (!file) {
@@ -67,6 +89,15 @@ int writeFile(std::string filename, std::vector<std::string> 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<char>(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();