From a6f32d4c49320376981be9560662bccbbd49b80b Mon Sep 17 00:00:00 2001 From: Arthur Barraux Date: Thu, 10 Oct 2024 02:09:48 +0200 Subject: [PATCH] Fully fonctional editing --- Makefile | 15 +++++---- README.md | 22 ++++++++++++ include/data.h | 1 + include/define.h | 16 +++++---- include/editor_op.h | 4 +++ include/input.h | 3 ++ include/row_op.h | 14 ++++++-- main.c | 2 +- src/editor_op.c | 38 +++++++++++++++++++-- src/file_io.c | 27 ++++++++++++--- src/init.c | 1 + src/input.c | 82 ++++++++++++++++++++++++++++++++++++++------- src/output.c | 5 +-- src/row_op.c | 56 ++++++++++++++++++++++++++++--- src/terminal.c | 10 +++--- 15 files changed, 249 insertions(+), 47 deletions(-) diff --git a/Makefile b/Makefile index c4ae1dd..4b99214 100644 --- a/Makefile +++ b/Makefile @@ -6,15 +6,11 @@ BELUGA_OUTPUT=bin -BUILD_DIR=build - BUILD_FLAGS=-Wall -Wextra -pedantic build: main.c src/* if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi - if [ ! -d doc/ ]; then mkdir doc; fi - $(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* - doxygen + $(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* $(BUILD_FLAGS) DEBUG_FLAGS=-Wall -Wextra -pedantic -Werror -fsanitize=address -g @@ -22,8 +18,15 @@ debug: main.c src/* if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi $(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* $(DEBUG_FLAGS) +doc: + if [ ! -d doc/ ]; then mkdir doc; fi + doxygen + clean: - rm -r $(BELUGA_OUTPUT)/* + rm -r $(BELUGA_OUTPUT) rm -rf doc/ + rm -rf tmp/ + +all: build doc # end diff --git a/README.md b/README.md index b45abe8..4b7a1f9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ # Beluga Beluga is a project of CLI text editor that will fit perfectly with your azerty keyboard. + +## Requirements + +You will only need **make** or **gcc** to compile the editor. + +## Installation + +Here is the installation line : +```git clone https://github.com/le-cocotier/beluga.git ~/.beluga && cd ~/.beluga && make build``` + +The executable file will be in `bin/beluga`. Feel free to add it to your path. + +You can either run `make all` if you're interested by the doxygen documentation. + +## Getting start + +To open an existing file just type : +```beluga path_to_my_file``` + +The only keybinds that you will need will be : + - Ctrl-Q : leave the editor + - Ctrl-S : Save a file diff --git a/include/data.h b/include/data.h index 3e625a0..1effdf0 100644 --- a/include/data.h +++ b/include/data.h @@ -30,6 +30,7 @@ struct editorConfig { int screencols; /**< Terminal width*/ int numrows; /**< Number of rows contained */ erow *row; /**< Store all the rows printed */ + int dirty; char *filename; char status_msg[80]; time_t status_msg_time; diff --git a/include/define.h b/include/define.h index 7c3a097..8af4442 100644 --- a/include/define.h +++ b/include/define.h @@ -3,6 +3,7 @@ #define CTRL_KEY(k) ((k) & 0x1f) +#define ESCAPE '\x1b' #define CURSOR_TOP_LEFT "\x1b[H" #define HIDE_CURSOR "\x1b[?25l" #define SHOW_CURSOR "\x1b[?25h" @@ -10,12 +11,12 @@ enum editorKey { BACKSPACE = 127, - CURSOR_LEFT = 1000, - CURSOR_RIGHT, - CURSOR_UP, - CURSOR_DOWN, + ARROW_LEFT = 1000, + ARROW_RIGHT, + ARROW_UP, + ARROW_DOWN, + DEL_KEY, BEG_LINE, - DELETE, END_LINE, PAGE_UP, PAGE_DOWN, @@ -23,7 +24,8 @@ enum editorKey { #define ABUF_INIT {NULL, 0} -#define BELUGA_VERSION "0.1" -#define TAB_LENGTH 8 +#define BELUGA_VERSION "1.0" +#define TAB_LENGTH 4 +#define QUIT_TIMES 1 #endif // DEFINE_H_ diff --git a/include/editor_op.h b/include/editor_op.h index 3562b37..93aa2a8 100644 --- a/include/editor_op.h +++ b/include/editor_op.h @@ -4,4 +4,8 @@ #include "data.h" void editorInsertChar(struct editorConfig *E, int c); +void editorInsertNewLine(struct editorConfig *E); + +void editorDelChar(struct editorConfig *E); + #endif // EDITOR_OP_H_ diff --git a/include/input.h b/include/input.h index 1b27d3a..7003ff9 100644 --- a/include/input.h +++ b/include/input.h @@ -3,6 +3,7 @@ #include "data.h" #include "define.h" +#include "output.h" #include "terminal.h" #include @@ -18,6 +19,8 @@ // END \x1b[4~ || [8~ || [F || OF // DELETE \x1b[3~ +char *editorPrompt(struct editorConfig *E, char *prompt); + void editorMoveCursor(struct editorConfig *E, int key); /** diff --git a/include/row_op.h b/include/row_op.h index 97e0a34..911603f 100644 --- a/include/row_op.h +++ b/include/row_op.h @@ -5,14 +5,24 @@ #include "define.h" #include #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 editorInsertRow(struct editorConfig *E, int at, char *s, size_t len); -void editorRowInsertChar(erow *row, int at, int c); +void editorFreeRow(erow *row); + +void editorDelRow(struct editorConfig *E, int at); + +void editorRowInsertChar(struct editorConfig *E, erow *row, int at, int c); + +void editorRowAppendString(struct editorConfig *E, erow *row, char *s, + size_t len); + +void editorRowDelchar(struct editorConfig *E, erow *row, int at); #endif // ROW_OP_H_ diff --git a/main.c b/main.c index 572673f..f720ceb 100644 --- a/main.c +++ b/main.c @@ -26,7 +26,7 @@ int main(int argc, char *argv[]) { editorOpen(&E, argv[1]); } - editorSetStatusMessage(&E, "HELP: Ctrl-Q = quit"); + editorSetStatusMessage(&E, "HELP: Ctrl-S = save | Ctrl-Q = quit"); while (1) { editorRefreshScreen(&E); diff --git a/src/editor_op.c b/src/editor_op.c index cfdf118..d5aa1aa 100644 --- a/src/editor_op.c +++ b/src/editor_op.c @@ -3,8 +3,42 @@ void editorInsertChar(struct editorConfig *E, int c) { if (E->cursor_y == E->numrows) { - editorAppendRow(E, "", 0); + editorInsertRow(E, E->numrows, "", 0); } - editorRowInsertChar(&E->row[E->cursor_y], E->cursor_x, c); + editorRowInsertChar(E, &E->row[E->cursor_y], E->cursor_x, c); E->cursor_x++; } + +void editorInsertNewLine(struct editorConfig *E) { + erow *row; + if (!E->cursor_x) { + editorInsertRow(E, E->cursor_y, "", 0); + } else { + row = &E->row[E->cursor_y]; + editorInsertRow(E, E->cursor_y + 1, &row->chars[E->cursor_x], + row->size - E->cursor_x); + row = &E->row[E->cursor_y]; + row->size = E->cursor_x; + row->chars[row->size] = '\0'; + editorUpdateRow(row); + } + ++E->cursor_y; + E->cursor_x = 0; +} + +void editorDelChar(struct editorConfig *E) { + erow *row; + if (E->cursor_y == E->numrows || !(E->cursor_x || E->cursor_y)) { + return; + } + row = &E->row[E->cursor_y]; + if (E->cursor_x > 0) { + editorRowDelchar(E, row, E->cursor_x - 1); + --E->cursor_x; + } else { + E->cursor_x = E->row[E->cursor_y - 1].size; + editorRowAppendString(E, &E->row[E->cursor_y - 1], row->chars, row->size); + editorDelRow(E, E->cursor_y); + --E->cursor_y; + } +} diff --git a/src/file_io.c b/src/file_io.c index c76d331..0bde548 100644 --- a/src/file_io.c +++ b/src/file_io.c @@ -1,4 +1,6 @@ #include "../include/file_io.h" +#include "../include/input.h" +#include "../include/output.h" #include #include #include @@ -47,10 +49,11 @@ void editorOpen(struct editorConfig *E, char *filename) { (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { --line_len; } - editorAppendRow(E, line, line_len); + editorInsertRow(E, E->numrows, line, line_len); } free(line); fclose(fp); + E->dirty = 0; } void editorSave(struct editorConfig *E) { @@ -58,12 +61,26 @@ void editorSave(struct editorConfig *E) { char *buf; int fd; if (E->filename == NULL) { - return; + E->filename = editorPrompt(E, "Save as: %s (ESC to cancel)"); + if (E->filename == NULL) { + editorSetStatusMessage(E, "Save aborted"); + return; + } } buf = editorRowsToString(E, &len); fd = open(E->filename, O_RDWR | O_CREAT, 0644); - ftruncate(fd, len); - write(fd, buf, len); - close(fd); + if (fd != -1) { + if (ftruncate(fd, len) != -1) { + if (write(fd, buf, len) == len) { + close(fd); + free(buf); + E->dirty = 0; + editorSetStatusMessage(E, "%d bytes written to disk", len); + return; + } + } + close(fd); + } free(buf); + editorSetStatusMessage(E, "Can't save! I/O error: %s", strerror(errno)); } diff --git a/src/init.c b/src/init.c index cd9aac5..9b06fac 100644 --- a/src/init.c +++ b/src/init.c @@ -8,6 +8,7 @@ void initEditor(struct editorConfig *E) { E->col_offset = 0; E->numrows = 0; E->row = NULL; + E->dirty = 0; E->filename = NULL; E->status_msg[0] = '\0'; E->status_msg_time = 0; diff --git a/src/input.c b/src/input.c index 61f73f7..9fc3d12 100644 --- a/src/input.c +++ b/src/input.c @@ -1,13 +1,58 @@ #include "../include/input.h" #include "../include/editor_op.h" #include "../include/file_io.h" +#include "../include/output.h" +#include #include +#include +#include +#include + +/** + * \fn char * editorPrompt(struct editorConfig *E, char *prompt) + * \brief Return user input in a prompt when enter is hit. */ + +char *editorPrompt(struct editorConfig *E, char *prompt) { + size_t buf_size = 128; + char *buf = malloc(buf_size); + size_t buf_len = 0; + int c = 0; + buf[0] = '\0'; + + while (1) { + editorSetStatusMessage(E, prompt, buf); + editorRefreshScreen(E); + c = editorReadKey(); + if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { + if (buf_len != 0) { + buf[--buf_len] = '\0'; + } + } else if (c == ESCAPE) { + fprintf(stderr, "escape"); + editorSetStatusMessage(E, ""); + free(buf); + return NULL; + } else if (c == '\r') { + if (buf_len != 0) { + editorSetStatusMessage(E, ""); + return buf; + } + } else if (!iscntrl(c) && c < 128) { + if (buf_len == buf_size - 1) { + buf_size *= 2; + buf = realloc(buf, buf_size); + } + buf[buf_len++] = c; + buf[buf_len] = '\0'; + } + } +} 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: + case ARROW_RIGHT: if (row && E->cursor_x < row->size) { ++E->cursor_x; } else if (row && E->cursor_x == row->size) { @@ -15,17 +60,17 @@ void editorMoveCursor(struct editorConfig *E, int key) { E->cursor_x = 0; } break; - case CURSOR_DOWN: + case ARROW_DOWN: if (E->cursor_y < E->numrows) { ++E->cursor_y; } break; - case CURSOR_UP: + case ARROW_UP: if (E->cursor_y != 0) { --E->cursor_y; } break; - case CURSOR_LEFT: + case ARROW_LEFT: if (E->cursor_x != 0) { --E->cursor_x; } else if (E->cursor_y > 0) { @@ -43,14 +88,23 @@ void editorMoveCursor(struct editorConfig *E, int key) { } void editorProcessKeypress(struct editorConfig *E) { + static int quit_times = QUIT_TIMES; int c = editorReadKey(); int times; switch (c) { + case '\r': - /* TODO */ + editorInsertNewLine(E); break; case CTRL_KEY('q'): + if (E->dirty && quit_times > 0) { + editorSetStatusMessage(E, + "WARNING! Changes hasn't been saved. Press Ctrl-Q " + "another time to quit."); + --quit_times; + return; + } write(STDOUT_FILENO, "\x1b[2J", 4); write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); disableRawMode(E); @@ -73,8 +127,11 @@ void editorProcessKeypress(struct editorConfig *E) { case BACKSPACE: case CTRL_KEY('h'): - // case DEL_KEY: - /* TODO */ + case DEL_KEY: + if (c == DEL_KEY) { + editorMoveCursor(E, ARROW_RIGHT); + } + editorDelChar(E); break; case PAGE_UP: @@ -89,14 +146,14 @@ void editorProcessKeypress(struct editorConfig *E) { } times = E->screenrows; while (--times) { - editorMoveCursor(E, c == PAGE_UP ? CURSOR_UP : CURSOR_DOWN); + editorMoveCursor(E, c == PAGE_UP ? ARROW_UP : ARROW_DOWN); } } break; - case CURSOR_UP: - case CURSOR_DOWN: - case CURSOR_LEFT: - case CURSOR_RIGHT: + case ARROW_UP: + case ARROW_DOWN: + case ARROW_LEFT: + case ARROW_RIGHT: editorMoveCursor(E, c); break; @@ -107,4 +164,5 @@ void editorProcessKeypress(struct editorConfig *E) { editorInsertChar(E, c); break; } + quit_times = QUIT_TIMES; } diff --git a/src/output.c b/src/output.c index 038bdd0..28b508d 100644 --- a/src/output.c +++ b/src/output.c @@ -71,8 +71,9 @@ void editorDrawStatusBar(struct editorConfig *E, struct abuf *ab) { 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); + len = snprintf(status, sizeof(status), "%.20s - %d lines%s", + E->filename ? E->filename : "[No Name]", E->numrows, + E->dirty ? "*" : ""); render_len = snprintf(render_status, sizeof(render_status), "%d/%d", E->cursor_y + 1, E->numrows); if (len > E->screencols) { diff --git a/src/row_op.c b/src/row_op.c index a8f0101..1d902c3 100644 --- a/src/row_op.c +++ b/src/row_op.c @@ -1,6 +1,7 @@ #include "../include/row_op.h" #include #include +#include int editorRowCxToRx(erow *row, int cursor_x) { int render_x = 0; @@ -52,11 +53,14 @@ void editorUpdateRow(erow *row) { row->rsize = i_render; } -void editorAppendRow(struct editorConfig *E, char *s, size_t len) { - int at; - E->row = realloc(E->row, sizeof(erow) * (E->numrows + 1)); +void editorInsertRow(struct editorConfig *E, int at, char *s, size_t len) { + if (at < 0 || at > E->numrows) { + return; + } + + E->row = realloc(E->row, sizeof(erow) * (E->numrows + 1)); + memmove(&E->row[at + 1], &E->row[at], sizeof(erow) * (E->numrows - at)); - at = E->numrows; E->row[at].size = len; E->row[at].chars = malloc(len + 1); memcpy(E->row[at].chars, s, len); @@ -67,13 +71,29 @@ void editorAppendRow(struct editorConfig *E, char *s, size_t len) { editorUpdateRow(&E->row[at]); ++E->numrows; + ++E->dirty; +} + +void editorFreeRow(erow *row) { + free(row->render); + free(row->chars); +} + +void editorDelRow(struct editorConfig *E, int at) { + if (at < 0 || at >= E->numrows) { + return; + } + editorFreeRow(&E->row[at]); + memmove(&E->row[at], &E->row[at + 1], sizeof(erow) * (E->numrows - at - 1)); + --E->numrows; + ++E->dirty; } /** * \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) { +void editorRowInsertChar(struct editorConfig *E, erow *row, int at, int c) { if (at < 0 || at > row->size) { at = row->size; } @@ -82,4 +102,30 @@ void editorRowInsertChar(erow *row, int at, int c) { ++row->size; row->chars[at] = c; editorUpdateRow(row); + ++E->dirty; +} + +void editorRowAppendString(struct editorConfig *E, erow *row, char *s, + size_t len) { + row->chars = realloc(row->chars, row->size + len + 1); + memcpy(&row->chars[row->size], s, len); + row->size += len; + row->chars[row->size] = '\0'; + editorUpdateRow(row); + ++E->dirty; +} + +/** + * \fn editorRowDelChar(struct editorConfig *E, erow *erow, int at) + * \brief Delete the a char at the chosen position on the given row + * \param at Index of the char to delete + * \param row Row on operation is made */ +void editorRowDelchar(struct editorConfig *E, erow *row, int at) { + if (at < 0 || at >= row->size) { + return; + } + memmove(&row->chars[at], &row->chars[at + 1], row->size - at); + --row->size; + editorUpdateRow(row); + ++E->dirty; } diff --git a/src/terminal.c b/src/terminal.c index 2155394..581deb1 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -56,7 +56,7 @@ int editorReadKey() { case '1': return BEG_LINE; case '3': - return DELETE; + return DEL_KEY; case '4': return END_LINE; case '5': @@ -73,13 +73,13 @@ int editorReadKey() { switch (seq[1]) { case 'A': - return CURSOR_UP; + return ARROW_UP; case 'B': - return CURSOR_DOWN; + return ARROW_DOWN; case 'C': - return CURSOR_RIGHT; + return ARROW_RIGHT; case 'D': - return CURSOR_LEFT; + return ARROW_LEFT; case 'H': return BEG_LINE; case 'F':