diff --git a/.gitignore b/.gitignore index 9cda858..ab89d79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,60 +1,2 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -# documentation - -doc/* - - -# build directory -beluga-output/ +tmp/* +bin/* diff --git a/Doxyfile b/Doxyfile index 3d08a88..c1a94f3 100644 --- a/Doxyfile +++ b/Doxyfile @@ -54,7 +54,7 @@ PROJECT_NUMBER = # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = "Beluga is a code editor taylor made for azerty keyboards." # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -1006,56 +1006,8 @@ INPUT_FILE_ENCODING = # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cxxm \ - *.cpp \ - *.cppm \ - *.ccm \ - *.c++ \ - *.c++m \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.ixx \ - *.l \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f18 \ - *.f \ - *.for \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.ice + *.md # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -2344,7 +2296,7 @@ XML_NS_MEMB_FILE_SCOPE = NO # that can be used to generate PDF. # The default value is: NO. -GENERATE_DOCBOOK = YES +GENERATE_DOCBOOK = NO # The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be put in diff --git a/Makefile b/Makefile index 03ddf6c..ab04366 100644 --- a/Makefile +++ b/Makefile @@ -4,29 +4,26 @@ # @file # @version 0.1 -BELUGA_OUTPUT=beluga-output +BELUGA_OUTPUT=bin BUILD_DIR=build BUILD_FLAGS=-Wall -Wextra -pedantic -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) +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 -DEBUG_DIR=debug - DEBUG_FLAGS=-Wall -Wextra -pedantic -Werror -fsanitize=address,undefined -g -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) +debug: main.c src/* + if [ ! -d $(BELUGA_OUTPUT) ]; then mkdir $(BELUGA_OUTPUT); fi + $(CC) main.c -o $(BELUGA_OUTPUT)/beluga src/* $(DEBUG_FLAGS) clean: - rm -rf $(BELUGA_OUTPUT) + rm -r $(BELUGA_OUTPUT)/* rm -rf doc/ # end diff --git a/include/append_buffer.h b/include/append_buffer.h new file mode 100644 index 0000000..3f28992 --- /dev/null +++ b/include/append_buffer.h @@ -0,0 +1,12 @@ +#ifndef APPEND_BUFFER_H_ +#define APPEND_BUFFER_H_ + +#include "data.h" +#include +#include + +void abAppend(struct abuf *ab, const char *s, int len); + +void abFree(struct abuf *ab); + +#endif // APPEND_BUFFER_H_ diff --git a/include/data.h b/include/data.h new file mode 100644 index 0000000..20fbcb9 --- /dev/null +++ b/include/data.h @@ -0,0 +1,30 @@ +#ifndef DATA_H_ +#define DATA_H_ + +#include + +typedef struct erow { + int size; + char *chars; +} erow; + +/** + * \struct editorConfig + * \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; +}; + +struct abuf { + char *b; + int len; +}; + +#endif diff --git a/include/define.h b/include/define.h new file mode 100644 index 0000000..080493e --- /dev/null +++ b/include/define.h @@ -0,0 +1,27 @@ +#ifndef DEFINE_H_ +#define DEFINE_H_ + +#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" + +enum editorKey { + CURSOR_LEFT = 1000, + CURSOR_RIGHT, + CURSOR_UP, + CURSOR_DOWN, + BEG_LINE, + DELETE, + END_LINE, + PAGE_UP, + PAGE_DOWN, +}; + +#define ABUF_INIT {NULL, 0} + +#define BELUGA_VERSION "0.1" + +#endif // DEFINE_H_ diff --git a/include/file_io.h b/include/file_io.h new file mode 100644 index 0000000..7434098 --- /dev/null +++ b/include/file_io.h @@ -0,0 +1,13 @@ +#ifndef FILE_IO_H_ +#define FILE_IO_H_ + +#include "data.h" +#include "row_op.h" +#include "terminal.h" +#include +#include +#include + +void editorOpen(struct editorConfig *E, char *filename); + +#endif // FILE_IO_H_ diff --git a/include/init.h b/include/init.h new file mode 100644 index 0000000..af1f94e --- /dev/null +++ b/include/init.h @@ -0,0 +1,15 @@ +#ifndef INIT_H_ +#define INIT_H_ + +#include "data.h" +#include "terminal.h" +#include + +/** + * \fn void initEditor() + * \brief Job's function is to initialize all the fields of editorConfig. + * */ + +void initEditor(struct editorConfig *E); + +#endif // INIT_H_ diff --git a/include/input.h b/include/input.h new file mode 100644 index 0000000..ab43a38 --- /dev/null +++ b/include/input.h @@ -0,0 +1,18 @@ +#ifndef INPUT_H_ +#define INPUT_H_ + +#include "data.h" +#include "define.h" +#include "terminal.h" +#include + +void editorMoveCursor(struct editorConfig *E, int key); + +/** + * \fn void editorProcessKeypress() + * \brief Get the last key input and do the proper action. + */ + +void editorProcessKeypress(struct editorConfig *E); + +#endif // INPUT_H_ diff --git a/include/output.h b/include/output.h new file mode 100644 index 0000000..934b977 --- /dev/null +++ b/include/output.h @@ -0,0 +1,19 @@ +#ifndef OUTPUT_H_ +#define OUTPUT_H_ + +#include "append_buffer.h" +#include "data.h" +#include "define.h" +#include +#include + +/** + * \fn void editorDrawRows(struct editorConfig *E, struct abuf *ab) + * \brief Draws left rows of the editor. + */ + +void editorDrawRows(struct editorConfig *E, struct abuf *ab); + +void editorRefreshScreen(struct editorConfig *E); + +#endif // OUTPUT_H_ diff --git a/include/row_op.h b/include/row_op.h new file mode 100644 index 0000000..a3f395d --- /dev/null +++ b/include/row_op.h @@ -0,0 +1,11 @@ +#ifndef ROW_OP_H_ +#define ROW_OP_H_ + +#include "data.h" +#include +#include +#include + +void editorAppendRow(struct editorConfig *E, char *s, size_t len); + +#endif // ROW_OP_H_ diff --git a/include/terminal.h b/include/terminal.h new file mode 100644 index 0000000..cefe14e --- /dev/null +++ b/include/terminal.h @@ -0,0 +1,34 @@ + +#ifndef TERMINAL_H_ +#define TERMINAL_H_ + +/* includes */ + +#include "data.h" +#include "define.h" +#include +#include +#include +#include +#include +#include + +/** + * \fn void die(const char *s) + * \brief Exit program and return s error message. + * \param *s Error string + * */ + +void die(const char *s); + +void disableRawMode(struct editorConfig *E); + +void enableRawMode(struct editorConfig *E); + +int editorReadKey(); + +int getCursorPosition(int *rows, int *cols); + +int getWindowSize(int *rows, int *cols); + +#endif diff --git a/main.c b/main.c index d6d3cc6..ea2498a 100644 --- a/main.c +++ b/main.c @@ -5,339 +5,30 @@ * interactions. \version 0.1 \date 21 septembre 2024 */ -#include -#include -#include -#include -#include -#include -#include -#include -#include +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE -/* defines */ -#define BELUGA_VERSION "0.1" +#include "include/data.h" +#include "include/file_io.h" +#include "include/init.h" +#include "include/input.h" +#include "include/output.h" +#include "include/terminal.h" -#define CTRL_KEY(k) ((k) & 0x1f) +int main(int argc, char *argv[]) { -#define CURSOR_TOP_LEFT "\x1b[H" -#define HIDE_CURSOR "\x1b[?25l" -#define SHOW_CURSOR "\x1b[?25h" -#define ERASE_END_LINE "\x1b[K" + struct editorConfig E; -#define ABUF_INIT {NULL, 0} - -enum editorKey { - CURSOR_UP = 1000, - CURSOR_DOWN, - CURSOR_LEFT, - CURSOR_RIGHT, - PAGE_UP, - PAGE_DOWN -}; - -/* data */ -/** - * \struct editorConfig - * \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 */ - -/** - * \fn void die(const char *s) - * \brief Exit program and return s error message. - * \param *s Error string - * */ -void die(const char *s) { - write(STDOUT_FILENO, "\x1b[2J", 4); - write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); - perror(s); - exit(1); -} - -void disableRawMode() { - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) { - die("tcsetattr"); + enableRawMode(&E); + initEditor(&E); + if (argc >= 2) { + editorOpen(&E, argv[1]); } -} - -void enableRawMode() { - if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) { - die("tcgetattr"); - } - atexit(disableRawMode); - - struct termios raw = E.orig_termios; - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - raw.c_oflag &= ~(OPOST); - raw.c_cflag |= (CS8); - raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); - raw.c_cc[VMIN] = 0; - raw.c_cc[VTIME] = 1; - - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { - die("tcgetattr"); - } -} - -int editorReadKey() { - int nread; - char c; - char seq[3]; - while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { - if (nread == -1 && errno != EAGAIN) { - die("read"); - } - } - - 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) { - char buf[32]; - unsigned int i = 0; - - if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) { - return -1; - } - - while (i < sizeof(buf) - 1) { - if (read(STDIN_FILENO, &buf[i], 1) != 1) { - break; - } - if (buf[i] == 'R') { - break; - } - ++i; - } - buf[i] = '\0'; - - if (buf[0] != '\x1b' || buf[1] != '[') { - return -1; - } - if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) { - return -1; - } - - return 0; -} - -int getWindowSize(int *rows, int *cols) { - struct winsize ws; - - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) { - return -1; - } - return getCursorPosition(rows, cols); - } else { - *cols = ws.ws_col; - *rows = ws.ws_row; - return 0; - } -} - -/* append buffer */ -void abAppend(struct abuf *ab, const char *s, int len) { - char *new = realloc(ab->b, ab->len + len); - - 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(struct abuf *ab) - * \brief Draws left rows of the editor. - */ -void editorDrawRows(struct abuf *ab) { - int y; - char welcome[80]; - int welcome_len; - int padding; - for (y = 0; y < E.screenrows; ++y) { - 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() { - struct abuf ab = ABUF_INIT; - char buf[32]; - - 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 */ - -/** - * \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() { - int c = editorReadKey(); - int times; - - switch (c) { - case CTRL_KEY('q'): - 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; - } -} - -/* init */ - -/** - * \fn void initEditor() - * \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"); - } -} - -int main() { - - enableRawMode(); - initEditor(); while (1) { - editorRefreshScreen(); - editorProcessKeypress(); + editorRefreshScreen(&E); + editorProcessKeypress(&E); } return 0; } diff --git a/src/append_buffer.c b/src/append_buffer.c new file mode 100644 index 0000000..38cdc05 --- /dev/null +++ b/src/append_buffer.c @@ -0,0 +1,14 @@ +#include "../include/append_buffer.h" + +void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b, ab->len + len); + + if (new == NULL) { + return; + } + memcpy(&new[ab->len], s, len); + ab->b = new; + ab->len += len; +} + +void abFree(struct abuf *ab) { free(ab->b); } diff --git a/src/file_io.c b/src/file_io.c new file mode 100644 index 0000000..c0e1a39 --- /dev/null +++ b/src/file_io.c @@ -0,0 +1,21 @@ +#include "../include/file_io.h" + +void editorOpen(struct editorConfig *E, char *filename) { + FILE *fp = fopen(filename, "r"); + if (!fp) + die("fopen"); + + char *line = NULL; + size_t line_cap = 0; + ssize_t line_len; + + while ((line_len = getline(&line, &line_cap, fp)) != -1) { + while (line_len > 0 && + (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) { + --line_len; + } + editorAppendRow(E, line, line_len); + } + free(line); + fclose(fp); +} diff --git a/src/init.c b/src/init.c new file mode 100644 index 0000000..200bf1b --- /dev/null +++ b/src/init.c @@ -0,0 +1,12 @@ +#include "../include/init.h" + +void initEditor(struct editorConfig *E) { + E->cursor_x = 0; + E->cursor_y = 0; + E->row_offset = 0; + E->numrows = 0; + E->row = NULL; + if (getWindowSize(&E->screenrows, &E->screencols) == -1) { + die("getWindowSize"); + } +} diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..a97d1e6 --- /dev/null +++ b/src/input.c @@ -0,0 +1,63 @@ +#include "../include/input.h" + +void editorMoveCursor(struct editorConfig *E, 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(struct editorConfig *E) { + int c = editorReadKey(); + int times; + + switch (c) { + case CTRL_KEY('q'): + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); + disableRawMode(E); + exit(0); + break; + + case BEG_LINE: + E->cursor_x = 0; + break; + + case END_LINE: + E->cursor_x = E->screencols - 1; + break; + + case PAGE_UP: + case PAGE_DOWN: { + times = E->screenrows; + while (--times) { + editorMoveCursor(E, c == PAGE_UP ? CURSOR_UP : CURSOR_DOWN); + } + } break; + + case CURSOR_UP: + case CURSOR_DOWN: + case CURSOR_LEFT: + case CURSOR_RIGHT: + editorMoveCursor(E, c); + break; + } +} diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..a08adc0 --- /dev/null +++ b/src/output.c @@ -0,0 +1,60 @@ +#include "../include/output.h" + +void editorDrawRows(struct editorConfig *E, struct abuf *ab) { + int y; + char welcome[80]; + int welcome_len; + int padding; + int len; + int file_row; + for (y = 0; y < E->screenrows; ++y) { + file_row = y + E->row_offset; + if (file_row >= E->numrows) { + if (E->numrows == 0 && 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); + } + } else { + len = E->row[file_row].size > E->screencols ? E->screencols + : E->row[y].size; + abAppend(ab, E->row[file_row].chars, len); + } + abAppend(ab, ERASE_END_LINE, 3); + if (y < E->screenrows - 1) { + abAppend(ab, "\r\n", 2); + } + } +} + +void editorRefreshScreen(struct editorConfig *E) { + struct abuf ab = ABUF_INIT; + char buf[32]; + + abAppend(&ab, HIDE_CURSOR, 6); + abAppend(&ab, CURSOR_TOP_LEFT, 3); + + editorDrawRows(E, &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); +} diff --git a/src/row_op.c b/src/row_op.c new file mode 100644 index 0000000..55e9083 --- /dev/null +++ b/src/row_op.c @@ -0,0 +1,13 @@ +#include "../include/row_op.h" + +void editorAppendRow(struct editorConfig *E, char *s, size_t len) { + int at; + E->row = realloc(E->row, sizeof(erow) * (E->numrows + 1)); + + at = E->numrows; + E->row[at].size = len; + E->row[at].chars = malloc(len + 1); + memcpy(E->row[at].chars, s, len); + E->row[at].chars[len] = '\0'; + ++E->numrows; +} diff --git a/src/terminal.c b/src/terminal.c new file mode 100644 index 0000000..7d4261f --- /dev/null +++ b/src/terminal.c @@ -0,0 +1,145 @@ +#include "../include/terminal.h" + +void die(const char *s) { + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, CURSOR_TOP_LEFT, 3); + perror(s); + exit(1); +} + +void disableRawMode(struct editorConfig *E) { + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E->orig_termios) == -1) { + die("tcsetattr"); + } +} + +void enableRawMode(struct editorConfig *E) { + if (tcgetattr(STDIN_FILENO, &E->orig_termios) == -1) { + die("tcgetattr"); + } + + struct termios raw = E->orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { + die("tcgetattr"); + } +} + +int editorReadKey() { + int nread; + char c; + char seq[3]; + while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { + if (nread == -1 && errno != EAGAIN) { + die("read"); + } + } + + 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 '1': + return BEG_LINE; + case '3': + return DELETE; + case '4': + return END_LINE; + case '5': + return PAGE_UP; + case '6': + return PAGE_DOWN; + case '7': + return BEG_LINE; + case '8': + return END_LINE; + } + } + } else { + + switch (seq[1]) { + case 'A': + return CURSOR_UP; + case 'B': + return CURSOR_DOWN; + case 'C': + return CURSOR_LEFT; + case 'D': + return CURSOR_RIGHT; + case 'H': + return BEG_LINE; + case 'F': + return END_LINE; + } + } + } else if (seq[0] == 'O') { + switch (seq[1]) { + case 'H': + return BEG_LINE; + case 'F': + return END_LINE; + } + } + return '\x1b'; + } else { + return c; + } +} + +int getCursorPosition(int *rows, int *cols) { + char buf[32]; + unsigned int i = 0; + + if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) { + return -1; + } + + while (i < sizeof(buf) - 1) { + if (read(STDIN_FILENO, &buf[i], 1) != 1) { + break; + } + if (buf[i] == 'R') { + break; + } + ++i; + } + buf[i] = '\0'; + + if (buf[0] != '\x1b' || buf[1] != '[') { + return -1; + } + if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) { + return -1; + } + + return 0; +} + +int getWindowSize(int *rows, int *cols) { + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) { + return -1; + } + return getCursorPosition(rows, cols); + } else { + *cols = ws.ws_col; + *rows = ws.ws_row; + return 0; + } +}