diff --git a/Makefile b/Makefile index 0b885d8..03ddf6c 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ BUILD_DIR=build BUILD_FLAGS=-Wall -Wextra -pedantic -build: main.c main.h +build: main.c if [ ! -d beluga-output ]; then mkdir beluga-output; fi if [ ! -d beluga-output/build ]; then mkdir beluga-output/build; fi $(CC) main.c -o $(BELUGA_OUTPUT)/$(BUILD_DIR)/beluga $(BUILD_FLAGS) @@ -20,7 +20,7 @@ DEBUG_DIR=debug DEBUG_FLAGS=-Wall -Wextra -pedantic -Werror -fsanitize=address,undefined -g -debug: main.c main.h +debug: main.c if [ ! -d beluga-output ]; then mkdir beluga-output; fi if [ ! -d beluga-output/debug ]; then mkdir beluga-output/debug; fi $(CC) main.c -o $(BELUGA_OUTPUT)/$(DEBUG_DIR)/beluga $(DEBUG_FLAGS) diff --git a/main.c b/main.c index 780cd2e..d6d3cc6 100644 --- a/main.c +++ b/main.c @@ -5,19 +5,36 @@ * interactions. \version 0.1 \date 21 septembre 2024 */ -#include "main.h" #include -#include #include #include #include +#include #include #include +#include #include /* defines */ +#define BELUGA_VERSION "0.1" + #define CTRL_KEY(k) ((k) & 0x1f) + #define CURSOR_TOP_LEFT "\x1b[H" +#define HIDE_CURSOR "\x1b[?25l" +#define SHOW_CURSOR "\x1b[?25h" +#define ERASE_END_LINE "\x1b[K" + +#define ABUF_INIT {NULL, 0} + +enum editorKey { + CURSOR_UP = 1000, + CURSOR_DOWN, + CURSOR_LEFT, + CURSOR_RIGHT, + PAGE_UP, + PAGE_DOWN +}; /* data */ /** @@ -25,11 +42,17 @@ * \brief Containing our editor state. */ struct editorConfig { + int cursor_x, cursor_y; int screenrows; int screencols; struct termios orig_termios; }; +struct abuf { + char *b; + int len; +}; + struct editorConfig E; /* terminal */ @@ -40,7 +63,8 @@ struct editorConfig E; * \param *s Error string * */ void die(const char *s) { - wipeScreen(); + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); perror(s); exit(1); } @@ -70,15 +94,52 @@ void enableRawMode() { } } -char editorReadKey() { +int editorReadKey() { int nread; char c; + char seq[3]; while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { if (nread == -1 && errno != EAGAIN) { die("read"); } } - return c; + + if (c == '\x1b') { + if (read(STDIN_FILENO, &seq[0], 1) != 1 || + read(STDIN_FILENO, &seq[1], 1) != 1) { + return '\x1b'; + } + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + if (read(STDIN_FILENO, &seq[2], 1) != 1) { + return '\x1b'; + } + if (seq[2] == '~') { + switch (seq[1]) { + case '5': + return PAGE_UP; + case '6': + return PAGE_DOWN; + } + } + } else { + + switch (seq[1]) { + case 'A': + return CURSOR_UP; + case 'B': + return CURSOR_DOWN; + case 'C': + return CURSOR_LEFT; + case 'D': + return CURSOR_RIGHT; + } + } + } + return '\x1b'; + } else { + return c; + } } int getCursorPosition(int *rows, int *cols) { @@ -125,34 +186,74 @@ int getWindowSize(int *rows, int *cols) { } } -/* output */ +/* append buffer */ +void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b, ab->len + len); -/** \fn void wipeScreen() - * \brief Wipe the terminal screen and put the cursor on the top left corner. - * */ -void wipeScreen() { - /* Clear the screen */ - write(STDOUT_FILENO, "\x1b[2J", 4); - /* Put the cursor at the top left corner */ - write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); + if (new == NULL) { + return; + } + memcpy(&new[ab->len], s, len); + ab->b = new; + ab->len += len; } +void abFree(struct abuf *ab) { free(ab->b); } + +/* output */ + /** - * \fn void editorDrawRows() + * \fn void editorDrawRows(struct abuf *ab) * \brief Draws left rows of the editor. */ -void editorDrawRows() { +void editorDrawRows(struct abuf *ab) { int y; + char welcome[80]; + int welcome_len; + int padding; for (y = 0; y < E.screenrows; ++y) { - write(STDOUT_FILENO, "~\r\n", 3); + if (y == E.screenrows / 3) { + welcome_len = + snprintf(welcome, sizeof(welcome), "Beluga text editor -- version %s", + BELUGA_VERSION); + if (welcome_len > E.screencols) { + welcome_len = E.screencols; + } + padding = (E.screencols - welcome_len) / 2; + if (padding) { + abAppend(ab, "~", 1); + --padding; + } + while (padding--) { + abAppend(ab, " ", 1); + } + abAppend(ab, welcome, welcome_len); + } else { + abAppend(ab, "~", 1); + } + abAppend(ab, ERASE_END_LINE, 3); + if (y < E.screenrows - 1) { + abAppend(ab, "\r\n", 2); + } } } void editorRefreshScreen() { - wipeScreen(); - editorDrawRows(); + struct abuf ab = ABUF_INIT; + char buf[32]; - write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); + abAppend(&ab, HIDE_CURSOR, 6); + abAppend(&ab, CURSOR_TOP_LEFT, 3); + + editorDrawRows(&ab); + + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", E.cursor_y + 1, E.cursor_x + 1); + abAppend(&ab, buf, strlen(buf)); + + abAppend(&ab, SHOW_CURSOR, 6); + + write(STDOUT_FILENO, ab.b, ab.len); + abFree(&ab); } /* input */ @@ -161,14 +262,57 @@ void editorRefreshScreen() { * \fn void editorProcessKeypress() * \brief Get the last key input and do the proper action. */ + +void editorMoveCursor(int key) { + switch (key) { + case CURSOR_LEFT: + if (E.cursor_x != 0) { + --E.cursor_x; + } + break; + case CURSOR_DOWN: + if (E.cursor_x != E.screencols - 1) { + ++E.cursor_y; + } + break; + case CURSOR_UP: + if (E.cursor_y != 0) { + --E.cursor_y; + } + break; + case CURSOR_RIGHT: + if (E.cursor_y != E.screenrows - 1) { + ++E.cursor_x; + } + break; + } +} + void editorProcessKeypress() { - char c = editorReadKey(); + int c = editorReadKey(); + int times; switch (c) { case CTRL_KEY('q'): - wipeScreen(); + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); exit(0); break; + + case PAGE_UP: + case PAGE_DOWN: { + times = E.screenrows; + while (--times) { + editorMoveCursor(c == PAGE_UP ? CURSOR_UP : CURSOR_DOWN); + } + } break; + + case CURSOR_UP: + case CURSOR_DOWN: + case CURSOR_LEFT: + case CURSOR_RIGHT: + editorMoveCursor(c); + break; } } @@ -179,6 +323,8 @@ void editorProcessKeypress() { * \brief Job's function is to initialize all the fields of editorConfig. * */ void initEditor() { + E.cursor_x = 0; + E.cursor_y = 0; if (getWindowSize(&E.screenrows, &E.screencols) == -1) { die("getWindowSize"); } diff --git a/main.h b/main.h deleted file mode 100644 index 6a09166..0000000 --- a/main.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MAIN_H -#define MAIN_H -/* terminal */ - -void die(const char *s); -void disableRawMode(); -void enableRawMode(); -char editorReadKey(); - -/* output */ -void wipeScreen(); -void editorDrawRows(); -void editorRefreshScreen(); - -/* input */ -void editorProcessKeypress(); -#endif