Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dive
|
||||||
45
README.md
Normal file
45
README.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Dive
|
||||||
|
|
||||||
|
Dive is a Vim-like text editor using ncurses.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
dive file.txt
|
||||||
|
|
||||||
|
## Compiling
|
||||||
|
|
||||||
|
fish build.fish
|
||||||
|
|
||||||
|
## Supported actions
|
||||||
|
|
||||||
|
Ctrl+C exits the editor anywhere.
|
||||||
|
|
||||||
|
### Normal Mode
|
||||||
|
|
||||||
|
* Move around with arrow keys or hjkl
|
||||||
|
* Use i to enter insert mode
|
||||||
|
* Use a to enter insert mode, moving the cursor forward
|
||||||
|
* Use o to enter insert mode, with a blank line below the currently selected one
|
||||||
|
* Use : or ; to enter command mode
|
||||||
|
|
||||||
|
### Insert Mode
|
||||||
|
|
||||||
|
* Use ESC to get back to Normal Mode
|
||||||
|
* Use arrow keys to move around
|
||||||
|
* Type to type things in
|
||||||
|
* Backspace to delete the character before the cursor, or to merge 2 lines when cursor is at the start of a line
|
||||||
|
* Enter to split the current line in half
|
||||||
|
|
||||||
|
### Command Mode
|
||||||
|
|
||||||
|
* Use ESC to get back to Normal Mode
|
||||||
|
* Type to type a command into the minibuffer
|
||||||
|
* Enter to run the command
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
|
||||||
|
* q, quit: Quits Dive, forgetting any changes.
|
||||||
|
* w, write: Writes changes to the file provided
|
||||||
|
|
||||||
|
|
||||||
|
Enjoy!
|
||||||
3
build.fish
Normal file
3
build.fish
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env fish
|
||||||
|
|
||||||
|
g++ src/*.cpp -lncurses -o dive -ggdb -std=c++23
|
||||||
17
src/buffer.cpp
Normal file
17
src/buffer.cpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include "buffer.hpp"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
void Buffer::save() {
|
||||||
|
std::ofstream file(fileName);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Cannot open file " + fileName + " for writing");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
file << line << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/buffer.hpp
Normal file
40
src/buffer.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
enum class BufferType {
|
||||||
|
File
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Buffer {
|
||||||
|
BufferType type = BufferType::File;
|
||||||
|
std::string fileName = "";
|
||||||
|
std::vector<std::string> lines = {};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
size_t x = 0;
|
||||||
|
size_t y = 0;
|
||||||
|
} pos;
|
||||||
|
|
||||||
|
Buffer(std::string name) : fileName(name) {
|
||||||
|
if (std::filesystem::exists(name)) {
|
||||||
|
std::ifstream file(name);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Cannot open file " + name);
|
||||||
|
}
|
||||||
|
std::string buf;
|
||||||
|
while (std::getline(file, buf)) {
|
||||||
|
lines.push_back(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Buffer() = default;
|
||||||
|
|
||||||
|
void save();
|
||||||
|
};
|
||||||
|
}
|
||||||
252
src/editor.cpp
Normal file
252
src/editor.cpp
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#include "editor.hpp"
|
||||||
|
#include "renderer.hpp"
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstring>
|
||||||
|
#include <exception>
|
||||||
|
#include <ncurses.h>
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
void Editor::handleKey(Keycode key) {
|
||||||
|
switch (state) {
|
||||||
|
case EditorState::Normal: {
|
||||||
|
handleKeyNormal(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EditorState::Insert: {
|
||||||
|
handleKeyInsert(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EditorState::Command: {
|
||||||
|
handleKeyCommand(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Editor::handleKeyNormal(Keycode key) {
|
||||||
|
minibuf.clear();
|
||||||
|
switch (key) {
|
||||||
|
case 'i':
|
||||||
|
{
|
||||||
|
state = EditorState::Insert;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'a':
|
||||||
|
{
|
||||||
|
state = EditorState::Insert;
|
||||||
|
if (pos.x < buffer.lines[pos.y].size()) {
|
||||||
|
pos.x++;
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'o':
|
||||||
|
{
|
||||||
|
state = EditorState::Insert;
|
||||||
|
buffer.lines.insert(buffer.lines.begin() + pos.y + 1, "");
|
||||||
|
pos.y++;
|
||||||
|
pos.x = 0;
|
||||||
|
currentLineHangover = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ':':
|
||||||
|
case ';':
|
||||||
|
{
|
||||||
|
state = EditorState::Command;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_UP:
|
||||||
|
case 'k':
|
||||||
|
{
|
||||||
|
if (pos.y > 0) {
|
||||||
|
pos.y--;
|
||||||
|
if (pos.x > buffer.lines[pos.y].size()) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else if (buffer.lines[pos.y].size() < currentLineHangover) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else {
|
||||||
|
pos.x = currentLineHangover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
case 13:
|
||||||
|
case KEY_ENTER:
|
||||||
|
case KEY_DOWN:
|
||||||
|
case 'j':
|
||||||
|
{
|
||||||
|
if (pos.y < buffer.lines.size() - 1) {
|
||||||
|
pos.y++;
|
||||||
|
if (pos.x > buffer.lines[pos.y].size()) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else if (buffer.lines[pos.y].size() < currentLineHangover) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else {
|
||||||
|
pos.x = currentLineHangover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_LEFT:
|
||||||
|
case KEY_BACKSPACE:
|
||||||
|
case 'h':
|
||||||
|
{
|
||||||
|
if (pos.x > 0) {
|
||||||
|
pos.x--;
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_RIGHT:
|
||||||
|
case 'l':
|
||||||
|
{
|
||||||
|
if (pos.x < buffer.lines[pos.y].size()) {
|
||||||
|
pos.x++;
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Editor::handleKeyInsert(Keycode key) {
|
||||||
|
switch (key) {
|
||||||
|
case 27: // ESC
|
||||||
|
{
|
||||||
|
state = EditorState::Normal;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KEY_UP:
|
||||||
|
{
|
||||||
|
if (pos.y > 0) {
|
||||||
|
pos.y--;
|
||||||
|
if (pos.x > buffer.lines[pos.y].size()) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else if (buffer.lines[pos.y].size() < currentLineHangover) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else {
|
||||||
|
pos.x = currentLineHangover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_DOWN:
|
||||||
|
{
|
||||||
|
if (pos.y < buffer.lines.size() - 1) {
|
||||||
|
pos.y++;
|
||||||
|
if (pos.x > buffer.lines[pos.y].size()) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else if (buffer.lines[pos.y].size() < currentLineHangover) {
|
||||||
|
pos.x = buffer.lines[pos.y].size();
|
||||||
|
} else {
|
||||||
|
pos.x = currentLineHangover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_LEFT:
|
||||||
|
{
|
||||||
|
if (pos.x > 0) {
|
||||||
|
pos.x--;
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_RIGHT:
|
||||||
|
{
|
||||||
|
if (pos.x < buffer.lines[pos.y].size()) {
|
||||||
|
pos.x++;
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KEY_BACKSPACE:
|
||||||
|
{
|
||||||
|
if (pos.x > 0) {
|
||||||
|
buffer.lines[pos.y].erase(pos.x - 1, 1);
|
||||||
|
pos.x--;
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
} else {
|
||||||
|
if (pos.y == 0) break;
|
||||||
|
|
||||||
|
// Merge with previous line
|
||||||
|
pos.x = buffer.lines[pos.y - 1].size();
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
buffer.lines[pos.y - 1] += buffer.lines[pos.y];
|
||||||
|
buffer.lines.erase(buffer.lines.begin() + pos.y);
|
||||||
|
pos.y--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
case 13:
|
||||||
|
case KEY_ENTER:
|
||||||
|
{
|
||||||
|
std::string tail = buffer.lines[pos.y].substr(pos.x);
|
||||||
|
buffer.lines[pos.y] = buffer.lines[pos.y].substr(0, pos.x);
|
||||||
|
buffer.lines.insert(buffer.lines.begin() + pos.y + 1, tail);
|
||||||
|
pos.y++;
|
||||||
|
pos.x = 0;
|
||||||
|
currentLineHangover = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
if (key >= 32 && key < 127) {
|
||||||
|
char ch[2] = { (char)key, '\0' };
|
||||||
|
buffer.lines[pos.y].insert(pos.x, ch);
|
||||||
|
pos.x++;
|
||||||
|
currentLineHangover = pos.x;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Editor::handleKeyCommand(Keycode key) {
|
||||||
|
switch (key) {
|
||||||
|
case 27: // ESC
|
||||||
|
{
|
||||||
|
minibuf.clear();
|
||||||
|
state = EditorState::Normal;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 10:
|
||||||
|
case 13:
|
||||||
|
case KEY_ENTER:
|
||||||
|
{
|
||||||
|
if (minibuf == "q" || minibuf == "quit") {
|
||||||
|
// do this cleaner at some point
|
||||||
|
raise(SIGINT);
|
||||||
|
} else if (minibuf == "w" || minibuf == "write") {
|
||||||
|
try {
|
||||||
|
buffer.save();
|
||||||
|
minibuf = "Wrote to file " + buffer.fileName;
|
||||||
|
} catch (std::exception e) {
|
||||||
|
minibuf = e.what();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
minibuf = "Unknown command \"" + minibuf + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
state = EditorState::Normal;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
minibuf += keyname(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/editor.hpp
Normal file
42
src/editor.hpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "buffer.hpp"
|
||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
extern std::string diveHome;
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
enum class EditorState {
|
||||||
|
Normal, Insert, Command
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Editor {
|
||||||
|
EditorState state = EditorState::Normal;
|
||||||
|
Buffer buffer;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
} pos;
|
||||||
|
|
||||||
|
int currentLineHangover = 0;
|
||||||
|
std::string minibuf = "";
|
||||||
|
|
||||||
|
// Used to store the old position before interacting with the minibuf
|
||||||
|
struct {
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
} oldPos;
|
||||||
|
|
||||||
|
Editor(std::string file) : buffer(Buffer(file)) {}
|
||||||
|
Editor() : buffer(Buffer(diveHome)) {}
|
||||||
|
|
||||||
|
void handleKey(Keycode key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handleKeyNormal(Keycode key);
|
||||||
|
void handleKeyInsert(Keycode key);
|
||||||
|
void handleKeyCommand(Keycode key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
80
src/main.cpp
Normal file
80
src/main.cpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#include "editor.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static Dive::Window* _global_window = nullptr;
|
||||||
|
static bool hasCaught = false;
|
||||||
|
|
||||||
|
void exitHandle(int signum) {
|
||||||
|
|
||||||
|
if (hasCaught == true) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCaught = true;
|
||||||
|
|
||||||
|
if (_global_window == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_global_window->exit();
|
||||||
|
|
||||||
|
switch (signum) {
|
||||||
|
case SIGABRT:
|
||||||
|
std::cout << "Dive has unexpectedly aborted! (abort signal)\nPlease report this issue on Chookspace (https://chookspace.com/max/dive)" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
case SIGSEGV:
|
||||||
|
std::cout << "Dive has unexpectedly aborted! (segfault signal)\nPlease report this issue on Chookspace (https://chookspace.com/max/dive)" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
case SIGINT:
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::cout << "Dive has unexpectedly aborted! (signal number " << signum << ")\nPlease report this issue on Chookspace (https://chookspace.com/max/dive)" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string diveHome = "/.divehome";
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
signal(SIGINT, exitHandle);
|
||||||
|
signal(SIGABRT, exitHandle);
|
||||||
|
signal(SIGSEGV, exitHandle);
|
||||||
|
|
||||||
|
const char* home = getenv("HOME");
|
||||||
|
if (home == NULL) {
|
||||||
|
home = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
diveHome = home + std::string("/.divehome");
|
||||||
|
|
||||||
|
std::string fileName = diveHome;
|
||||||
|
|
||||||
|
if (argc > 1) {
|
||||||
|
fileName = argv[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Dive::Editor editor(fileName);
|
||||||
|
if (editor.buffer.lines.empty()) {
|
||||||
|
editor.buffer.lines.push_back("");
|
||||||
|
}
|
||||||
|
Dive::Window window(editor);
|
||||||
|
_global_window = &window;
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.show();
|
||||||
|
} catch (std::exception e) {
|
||||||
|
window.exit();
|
||||||
|
std::cout << "An exception occured in Dive:\n" << e.what() << "\nPlease report this issue on Chookspace (https://chookspace.com/max/dive)" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
47
src/renderer.cpp
Normal file
47
src/renderer.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "renderer.hpp"
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
void Renderer::init() {
|
||||||
|
if (!hasInit) {
|
||||||
|
initscr();
|
||||||
|
cbreak();
|
||||||
|
noecho();
|
||||||
|
keypad(stdscr, TRUE);
|
||||||
|
set_escdelay(10);
|
||||||
|
|
||||||
|
hasInit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::close() {
|
||||||
|
if (hasInit) endwin();
|
||||||
|
hasInit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::display() {
|
||||||
|
if (hasInit) refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::clear() {
|
||||||
|
if (hasInit) erase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Keycode Renderer::wait() {
|
||||||
|
if (hasInit) return getch();
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::putString(int x, int y, std::string str) {
|
||||||
|
if (hasInit) mvprintw(y, x, "%s", str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::appendString(std::string str) {
|
||||||
|
if (hasInit) printw("%s", str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::setPos(int x, int y) {
|
||||||
|
if (hasInit) move(y, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/renderer.hpp
Normal file
85
src/renderer.hpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
|
||||||
|
using Keycode = int;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Renderer struct
|
||||||
|
* Dive's abstraction over ncurses, which allows easy screen manipulation.
|
||||||
|
*
|
||||||
|
* Call Renderer().init() before using any other methods, this will set up the ncurses
|
||||||
|
* screen for you. All other methods will do nothing without the renderer being init'd.
|
||||||
|
*
|
||||||
|
* You don't have to do Renderer().close(), as the destructor will do this for you,
|
||||||
|
* unless you want to exit the window early. The Renderer will keep track of whether
|
||||||
|
* it is init'd, and can be reused after being closed by using init again.
|
||||||
|
*/
|
||||||
|
struct Renderer {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialises ncurses in the terminal. When called, will wipe the terminal and
|
||||||
|
* ensure it is ready for all other functions.
|
||||||
|
*
|
||||||
|
* This method sets up ncurses in the way which Dive expects it to function,
|
||||||
|
* please do not mess with ncurses while the renderer is init'ed.
|
||||||
|
*/
|
||||||
|
void init();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Closes ncurses in the terminal. Use when you're done. Automatically called by
|
||||||
|
* the destructor.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Displays anything currently in the buffer, waiting to be displayed.
|
||||||
|
*/
|
||||||
|
void display();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clears the buffer, ready for new contents.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Waits for the user to press a key on their keyboard.
|
||||||
|
* This blocks the thread until a key is pressed.
|
||||||
|
*/
|
||||||
|
Keycode wait();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Puts a string into a buffer, at the specified x and y positioning. Moves the
|
||||||
|
* cursor to the needed position.
|
||||||
|
*/
|
||||||
|
void putString(int x, int y, std::string str);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Puts a string into the buffer at the current position of the cursor. Useful
|
||||||
|
* for status bars.
|
||||||
|
*/
|
||||||
|
void appendString(std::string str);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets the position of the cursor on screen.
|
||||||
|
*/
|
||||||
|
void setPos(int x, int y);
|
||||||
|
|
||||||
|
Renderer() = default;
|
||||||
|
~Renderer() {
|
||||||
|
if (hasInit) close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensures that the renderer always knows if ncurses is ready or not.
|
||||||
|
*/
|
||||||
|
bool hasInit = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
71
src/window.cpp
Normal file
71
src/window.cpp
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#include "window.hpp"
|
||||||
|
#include "editor.hpp"
|
||||||
|
#include "renderer.hpp"
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
void Window::show() {
|
||||||
|
renderer.init();
|
||||||
|
|
||||||
|
getmaxyx(stdscr, size.h, size.w);
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
renderer.clear();
|
||||||
|
|
||||||
|
if (editor.pos.y < offset) {
|
||||||
|
offset = editor.pos.y;
|
||||||
|
} else if (editor.pos.y >= offset + (size.h - 2)) {
|
||||||
|
offset = editor.pos.y - (size.h - 2) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int viewLines = size.h - 2;
|
||||||
|
for (int i = 0; i < viewLines; i++) {
|
||||||
|
int lineIdx = offset + i;
|
||||||
|
if (lineIdx < (int)editor.buffer.lines.size()) {
|
||||||
|
renderer.putString(0, i, editor.buffer.lines[lineIdx]);
|
||||||
|
} else {
|
||||||
|
renderer.putString(0, i, "~");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create status bar
|
||||||
|
switch (editor.state) {
|
||||||
|
case EditorState::Normal:
|
||||||
|
renderer.putString(0, size.h - 2, "normal");
|
||||||
|
break;
|
||||||
|
case EditorState::Insert:
|
||||||
|
renderer.putString(0, size.h - 2, "insert");
|
||||||
|
break;
|
||||||
|
case EditorState::Command:
|
||||||
|
renderer.putString(0, size.h - 2, "command");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add minibuf to bottom of screen
|
||||||
|
renderer.putString(0, size.h - 1, editor.minibuf);
|
||||||
|
|
||||||
|
renderer.setPos(editor.pos.x, editor.pos.y - offset);
|
||||||
|
renderer.display();
|
||||||
|
|
||||||
|
Keycode key = renderer.wait();
|
||||||
|
switch (key) {
|
||||||
|
case KEY_RESIZE:
|
||||||
|
getmaxyx(stdscr, size.h, size.w);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
editor.handleKey(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::exit() {
|
||||||
|
renderer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/window.hpp
Normal file
37
src/window.hpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "editor.hpp"
|
||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
extern std::string diveHome;
|
||||||
|
|
||||||
|
namespace Dive {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Window class
|
||||||
|
* Holds information about the current window.
|
||||||
|
*
|
||||||
|
* This is the main class that needs to be interacted with by the main() function.
|
||||||
|
* It handles creating a renderer, keeping track of screen size, and key presses.
|
||||||
|
*/
|
||||||
|
class Window {
|
||||||
|
|
||||||
|
private:
|
||||||
|
Renderer renderer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Editor editor;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int w, h;
|
||||||
|
} size;
|
||||||
|
|
||||||
|
int currentBottomLine;
|
||||||
|
|
||||||
|
Window(Editor editor) : editor(editor) {}
|
||||||
|
Window() : editor(Editor(diveHome)) {}
|
||||||
|
|
||||||
|
void show();
|
||||||
|
void exit();
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user