From 1c22c3beca7e0b330cbad247dc5c93d15cdc205c Mon Sep 17 00:00:00 2001 From: Arthur Barraux Date: Tue, 8 Oct 2024 14:57:59 +0200 Subject: [PATCH] Status bar / editing / saving file --- Makefile | 2 +- include/data.h | 41 +++++++++++++++------ include/define.h | 2 ++ include/editor_op.h | 7 ++++ include/file_io.h | 4 +++ include/output.h | 9 +++++ include/row_op.h | 7 ++++ main.c | 2 ++ src/editor_op.c | 10 ++++++ src/file_io.c | 50 +++++++++++++++++++++++++- src/init.c | 6 ++++ src/input.c | 59 +++++++++++++++++++++++++++---- src/output.c | 86 +++++++++++++++++++++++++++++++++++++++++---- src/row_op.c | 72 +++++++++++++++++++++++++++++++++++++ src/terminal.c | 4 +-- 15 files changed, 334 insertions(+), 27 deletions(-) create mode 100644 include/editor_op.h create mode 100644 src/editor_op.c diff --git a/Makefile b/Makefile index ab04366..c4ae1dd 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ build: main.c src/* $(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* doxygen -DEBUG_FLAGS=-Wall -Wextra -pedantic -Werror -fsanitize=address,undefined -g +DEBUG_FLAGS=-Wall -Wextra -pedantic -Werror -fsanitize=address -g debug: main.c src/* if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi diff --git a/include/data.h b/include/data.h index 20fbcb9..3e625a0 100644 --- a/include/data.h +++ b/include/data.h @@ -2,10 +2,19 @@ #define DATA_H_ #include +#include + +/** + * \struct erow + * \brief Store one editor row + * \param + * */ typedef struct erow { - int size; - char *chars; + int size; /**< Size of the line */ + int rsize; /**< Size of the render line */ + char *chars; /**< Characters of the line */ + char *render; /**< The actual line we will print */ } erow; /** @@ -13,18 +22,28 @@ typedef struct erow { * \brief Containing our editor state. */ struct editorConfig { - int cursor_x, cursor_y; - int row_offset; - int screenrows; - int screencols; - int numrows; - erow *row; - struct termios orig_termios; + int cursor_x, cursor_y; /**< Cursor position */ + int rx; /**< Position in the render*/ + int row_offset; /**< Position scroll of lines */ + int col_offset; /**< Position scroll of colomns*/ + int screenrows; /**< Terminal height*/ + int screencols; /**< Terminal width*/ + int numrows; /**< Number of rows contained */ + erow *row; /**< Store all the rows printed */ + char *filename; + char status_msg[80]; + time_t status_msg_time; + struct termios orig_termios; /**< Terminal communication interface */ }; +/** + * \struct abuf + * \brief Contains text to add before writing to screen. + * */ + struct abuf { - char *b; - int len; + char *b; /**< Text that will be printed */ + int len; /**< Length of the text */ }; #endif diff --git a/include/define.h b/include/define.h index 080493e..7c3a097 100644 --- a/include/define.h +++ b/include/define.h @@ -9,6 +9,7 @@ #define ERASE_END_LINE "\x1b[K" enum editorKey { + BACKSPACE = 127, CURSOR_LEFT = 1000, CURSOR_RIGHT, CURSOR_UP, @@ -23,5 +24,6 @@ enum editorKey { #define ABUF_INIT {NULL, 0} #define BELUGA_VERSION "0.1" +#define TAB_LENGTH 8 #endif // DEFINE_H_ diff --git a/include/editor_op.h b/include/editor_op.h new file mode 100644 index 0000000..3562b37 --- /dev/null +++ b/include/editor_op.h @@ -0,0 +1,7 @@ +#ifndef EDITOR_OP_H_ +#define EDITOR_OP_H_ + +#include "data.h" +void editorInsertChar(struct editorConfig *E, int c); + +#endif // EDITOR_OP_H_ diff --git a/include/file_io.h b/include/file_io.h index 7434098..13eaa64 100644 --- a/include/file_io.h +++ b/include/file_io.h @@ -8,6 +8,10 @@ #include #include +char *editorRowsToString(struct editorConfig *E, int *buffer_len); + void editorOpen(struct editorConfig *E, char *filename); +void editorSave(struct editorConfig *E); + #endif // FILE_IO_H_ diff --git a/include/output.h b/include/output.h index 934b977..b6db4f1 100644 --- a/include/output.h +++ b/include/output.h @@ -4,6 +4,7 @@ #include "append_buffer.h" #include "data.h" #include "define.h" +#include "row_op.h" #include #include @@ -16,4 +17,12 @@ void editorDrawRows(struct editorConfig *E, struct abuf *ab); void editorRefreshScreen(struct editorConfig *E); +void editorScroll(struct editorConfig *E); + +void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab); + +void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab); + +void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...); + #endif // OUTPUT_H_ diff --git a/include/row_op.h b/include/row_op.h index a3f395d..97e0a34 100644 --- a/include/row_op.h +++ b/include/row_op.h @@ -2,10 +2,17 @@ #define ROW_OP_H_ #include "data.h" +#include "define.h" #include #include #include +int editorRowCxToRx(erow *row, int cursor_x); + +void editorUpdateRow(erow *row); + void editorAppendRow(struct editorConfig *E, char *s, size_t len); +void editorRowInsertChar(erow *row, int at, int c); + #endif // ROW_OP_H_ diff --git a/main.c b/main.c index ea2498a..572673f 100644 --- a/main.c +++ b/main.c @@ -26,6 +26,8 @@ int main(int argc, char *argv[]) { editorOpen(&E, argv[1]); } + editorSetStatusMessage(&E, "HELP: Ctrl-Q = quit"); + while (1) { editorRefreshScreen(&E); editorProcessKeypress(&E); diff --git a/src/editor_op.c b/src/editor_op.c new file mode 100644 index 0000000..cfdf118 --- /dev/null +++ b/src/editor_op.c @@ -0,0 +1,10 @@ +#include "../include/editor_op.h" +#include "../include/row_op.h" + +void editorInsertChar(struct editorConfig *E, int c) { + if (E->cursor_y == E->numrows) { + editorAppendRow(E, "", 0); + } + editorRowInsertChar(&E->row[E->cursor_y], E->cursor_x, c); + E->cursor_x++; +} diff --git a/src/file_io.c b/src/file_io.c index c0e1a39..c76d331 100644 --- a/src/file_io.c +++ b/src/file_io.c @@ -1,7 +1,40 @@ #include "../include/file_io.h" +#include +#include +#include +#include +#include +#include + +char *editorRowsToString(struct editorConfig *E, int *buffer_len) { + int tot_len = 0; + int j; + char *buf; + char *p; + + for (j = 0; j < E->numrows; ++j) { + tot_len += E->row[j].size + 1; + } + *buffer_len = tot_len; + buf = malloc(tot_len); + p = buf; + for (j = 0; j < E->numrows; ++j) { + memcpy(p, E->row[j].chars, E->row[j].size); + p += E->row[j].size; + *p = '\n'; + p++; + } + + return buf; +} void editorOpen(struct editorConfig *E, char *filename) { - FILE *fp = fopen(filename, "r"); + FILE *fp; + + free(E->filename); + E->filename = strdup(filename); + + fp = fopen(filename, "r"); if (!fp) die("fopen"); @@ -19,3 +52,18 @@ void editorOpen(struct editorConfig *E, char *filename) { free(line); fclose(fp); } + +void editorSave(struct editorConfig *E) { + int len; + char *buf; + int fd; + if (E->filename == NULL) { + return; + } + buf = editorRowsToString(E, &len); + fd = open(E->filename, O_RDWR | O_CREAT, 0644); + ftruncate(fd, len); + write(fd, buf, len); + close(fd); + free(buf); +} diff --git a/src/init.c b/src/init.c index 200bf1b..cd9aac5 100644 --- a/src/init.c +++ b/src/init.c @@ -3,10 +3,16 @@ void initEditor(struct editorConfig *E) { E->cursor_x = 0; E->cursor_y = 0; + E->rx = 0; E->row_offset = 0; + E->col_offset = 0; E->numrows = 0; E->row = NULL; + E->filename = NULL; + E->status_msg[0] = '\0'; + E->status_msg_time = 0; if (getWindowSize(&E->screenrows, &E->screencols) == -1) { die("getWindowSize"); } + E->screenrows -= 2; } diff --git a/src/input.c b/src/input.c index fb56a04..61f73f7 100644 --- a/src/input.c +++ b/src/input.c @@ -1,14 +1,22 @@ #include "../include/input.h" +#include "../include/editor_op.h" +#include "../include/file_io.h" +#include void editorMoveCursor(struct editorConfig *E, int key) { + erow *row = (E->cursor_y >= E->numrows) ? NULL : &E->row[E->cursor_y]; + int row_len; switch (key) { case CURSOR_RIGHT: - if (E->cursor_x != 0) { - --E->cursor_x; + if (row && E->cursor_x < row->size) { + ++E->cursor_x; + } else if (row && E->cursor_x == row->size) { + E->cursor_y++; + E->cursor_x = 0; } break; case CURSOR_DOWN: - if (E->cursor_y != E->screenrows - 1) { + if (E->cursor_y < E->numrows) { ++E->cursor_y; } break; @@ -18,11 +26,20 @@ void editorMoveCursor(struct editorConfig *E, int key) { } break; case CURSOR_LEFT: - if (E->cursor_x != E->screencols - 1) { - ++E->cursor_x; + if (E->cursor_x != 0) { + --E->cursor_x; + } else if (E->cursor_y > 0) { + --E->cursor_y; + E->cursor_x = E->row[E->cursor_y].size; } break; } + + row = (E->cursor_y >= E->numrows) ? NULL : &E->row[E->cursor_y]; + row_len = row ? row->size : 0; + if (E->cursor_x > row_len) { + E->cursor_x = row_len; + } } void editorProcessKeypress(struct editorConfig *E) { @@ -30,6 +47,9 @@ void editorProcessKeypress(struct editorConfig *E) { int times; switch (c) { + case '\r': + /* TODO */ + break; case CTRL_KEY('q'): write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); @@ -37,16 +57,36 @@ void editorProcessKeypress(struct editorConfig *E) { exit(0); break; + case CTRL_KEY('s'): + editorSave(E); + break; + case BEG_LINE: E->cursor_x = 0; break; case END_LINE: - E->cursor_x = E->screencols - 1; + if (E->cursor_y < E->numrows) { + E->cursor_x = E->row[E->cursor_y].size; + } + break; + + case BACKSPACE: + case CTRL_KEY('h'): + // case DEL_KEY: + /* TODO */ break; case PAGE_UP: case PAGE_DOWN: { + if (c == PAGE_UP) { + E->cursor_y = E->row_offset; + } else if (c == PAGE_DOWN) { + E->cursor_y = E->row_offset + E->screenrows - 1; + if (E->cursor_y > E->numrows) { + E->cursor_y = E->numrows; + } + } times = E->screenrows; while (--times) { editorMoveCursor(E, c == PAGE_UP ? CURSOR_UP : CURSOR_DOWN); @@ -59,5 +99,12 @@ void editorProcessKeypress(struct editorConfig *E) { case CURSOR_RIGHT: editorMoveCursor(E, c); break; + + case CTRL_KEY('l'): + case '\x1b': + break; + default: + editorInsertChar(E, c); + break; } } diff --git a/src/output.c b/src/output.c index a08adc0..038bdd0 100644 --- a/src/output.c +++ b/src/output.c @@ -1,4 +1,8 @@ #include "../include/output.h" +#include +#include +#include +#include void editorDrawRows(struct editorConfig *E, struct abuf *ab) { int y; @@ -30,18 +34,77 @@ void editorDrawRows(struct editorConfig *E, struct abuf *ab) { abAppend(ab, "~", 1); } } else { - len = E->row[file_row].size > E->screencols ? E->screencols - : E->row[y].size; - abAppend(ab, E->row[file_row].chars, len); + len = E->row[file_row].rsize - E->col_offset; + if (len < 0) + len = 0; + if (len > E->screencols) + len = E->screencols; + abAppend(ab, &E->row[file_row].render[E->col_offset], len); } abAppend(ab, ERASE_END_LINE, 3); - if (y < E->screenrows - 1) { - abAppend(ab, "\r\n", 2); + abAppend(ab, "\r\n", 2); + } +} + +void editorScroll(struct editorConfig *E) { + E->rx = E->cursor_x; + if (E->cursor_y < E->numrows) { + E->rx = editorRowCxToRx(&E->row[E->cursor_y], E->cursor_x); + } + + if (E->cursor_y < E->row_offset) { + E->row_offset = E->cursor_y; + } + if (E->cursor_y >= E->row_offset + E->screenrows) { + E->row_offset = E->cursor_y - E->screenrows + 1; + } + if (E->rx < E->col_offset) { + E->col_offset = E->rx; + } + if (E->rx >= E->col_offset + E->screencols) { + E->col_offset = E->rx - E->screencols + 1; + } +} + +void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab) { + int len, render_len; + char status[80], render_status[80]; + + abAppend(ab, "\x1b[7m", 4); // inverting colors + len = snprintf(status, sizeof(status), "%.20s - %d lines", + E->filename ? E->filename : "[No Name]", E->numrows); + render_len = snprintf(render_status, sizeof(render_status), "%d/%d", + E->cursor_y + 1, E->numrows); + if (len > E->screencols) { + len = E->screencols; + } + abAppend(ab, status, len); + while (len < E->screencols) { + if (E->screencols - len == render_len) { + abAppend(ab, render_status, render_len); + break; + } else { + abAppend(ab, " ", 1); + ++len; } } + abAppend(ab, "\x1b[m", 3); // normal text mode + abAppend(ab, "\r\n", 2); +} + +void editorDrawMessageBar(struct editorConfig *E, struct abuf *ab) { + int msg_len = strlen(E->status_msg); + abAppend(ab, ERASE_END_LINE, 3); + if (msg_len > E->screencols) { + msg_len = E->screencols; + } + if (msg_len && time(NULL) - E->status_msg_time < 5) { + abAppend(ab, E->status_msg, msg_len); + } } void editorRefreshScreen(struct editorConfig *E) { + editorScroll(E); struct abuf ab = ABUF_INIT; char buf[32]; @@ -49,8 +112,11 @@ void editorRefreshScreen(struct editorConfig *E) { abAppend(&ab, CURSOR_TOP_LEFT, 3); editorDrawRows(E, &ab); + editorDrawStatusBar(E, &ab); + editorDrawMessageBar(E, &ab); - snprintf(buf, sizeof(buf), "\x1b[%d;%dH", E->cursor_y + 1, E->cursor_x + 1); + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E->cursor_y - E->row_offset) + 1, + (E->rx - E->col_offset) + 1); abAppend(&ab, buf, strlen(buf)); abAppend(&ab, SHOW_CURSOR, 6); @@ -58,3 +124,11 @@ void editorRefreshScreen(struct editorConfig *E) { write(STDOUT_FILENO, ab.b, ab.len); abFree(&ab); } + +void editorSetStatusMessage(struct editorConfig *E, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vsnprintf(E->status_msg, sizeof(E->status_msg), fmt, ap); + va_end(ap); + E->status_msg_time = time(NULL); +} diff --git a/src/row_op.c b/src/row_op.c index 55e9083..a8f0101 100644 --- a/src/row_op.c +++ b/src/row_op.c @@ -1,4 +1,56 @@ #include "../include/row_op.h" +#include +#include + +int editorRowCxToRx(erow *row, int cursor_x) { + int render_x = 0; + int i; + for (i = 0; i < cursor_x; ++i) { + if (row->chars[i] == '\t') { + render_x += (TAB_LENGTH - 1) - (render_x % TAB_LENGTH); + } + render_x++; + } + return render_x; +} + +/** + * \fn editorUpdateRow(erow *row) + * \brief Copy content of \p row in \p row->render. + * */ + +void editorUpdateRow(erow *row) { + int i, i_render; + int tabs = 0; + + // counting number of tabs + + for (i = 0; i < row->size; ++i) { + tabs += + (row->chars[i] == '\t'); /**< increment tabs of 1 if chars[i] is one. */ + } + + free(row->render); + row->render = malloc(row->size + tabs * (TAB_LENGTH - 1) + + 1); /**< Tabs needs TAB_LENGTH chars so TAB_LENGTH - 1 + more than the first already counted. */ + + // end of counting + i_render = 0; + for (i = 0; i < row->size; ++i) { + if (row->chars[i] == '\t') { + row->render[i_render++] = ' '; + while (i_render % TAB_LENGTH) { + row->render[i_render++] = + ' '; /**< Addind the right amount of spaces for tabs */ + } + } else { + row->render[i_render++] = row->chars[i]; + } + } + row->render[i_render] = '\0'; // Don't forget the end of string character. + row->rsize = i_render; +} void editorAppendRow(struct editorConfig *E, char *s, size_t len) { int at; @@ -9,5 +61,25 @@ void editorAppendRow(struct editorConfig *E, char *s, size_t len) { E->row[at].chars = malloc(len + 1); memcpy(E->row[at].chars, s, len); E->row[at].chars[len] = '\0'; + + E->row[at].rsize = 0; + E->row[at].render = NULL; + editorUpdateRow(&E->row[at]); + ++E->numrows; } + +/** + * \fn editorRowInsertChar(erow *row, int at, int c) + * \param at Index of where we want to insert the char */ + +void editorRowInsertChar(erow *row, int at, int c) { + if (at < 0 || at > row->size) { + at = row->size; + } + row->chars = realloc(row->chars, row->size + 2); + memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); + ++row->size; + row->chars[at] = c; + editorUpdateRow(row); +} diff --git a/src/terminal.c b/src/terminal.c index 7d4261f..2155394 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -77,9 +77,9 @@ int editorReadKey() { case 'B': return CURSOR_DOWN; case 'C': - return CURSOR_LEFT; - case 'D': return CURSOR_RIGHT; + case 'D': + return CURSOR_LEFT; case 'H': return BEG_LINE; case 'F':