From a733fc40c50094ebb1b71cc7ecb0ef7cf663fd27 Mon Sep 17 00:00:00 2001 From: GrenderG Date: Thu, 6 Apr 2017 16:18:14 +0200 Subject: [PATCH] You can now open files and. Also scrolling is implemented. --- tte.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 208 insertions(+), 22 deletions(-) diff --git a/tte.c b/tte.c index 1b04402..3009466 100644 --- a/tte.c +++ b/tte.c @@ -1,11 +1,22 @@ /*** Include section ***/ +// We add them above our includes, because the header +// files we’re including use the macros to decide what +// features to expose. These macros remove some compilation +// warnings. See +// https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html +// for more info. +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE + #include #include #include #include #include #include +#include #include #include @@ -18,14 +29,28 @@ #define ABUF_INIT {NULL, 0} // Version code #define TTE_VERSION "0.0.1" +// Length of a tab stop +#define TTE_TAB_STOP 8 /*** Data section ***/ +typedef struct editor_row { + int size; + int render_size; + char* chars; + char* render; +} editor_row; + struct editor_config { int cursor_x; int cursor_y; - int rows; - int cols; + int render_x; + int row_offset; + int col_offset; + int screen_rows; + int screen_cols; + int num_rows; + editor_row* row; struct termios orig_termios; } ec; @@ -38,7 +63,7 @@ struct a_buf { }; enum editor_key { - ARROW_LEFT = 0x3e8, // 1000, large value out of range of a char. + ARROW_LEFT = 0x3e8, // 1000, large value out of the range of a char. ARROW_RIGHT, ARROW_UP, ARROW_DOWN, @@ -175,7 +200,7 @@ int editorReadKey() { } } -int getWindowSize(int* rows, int* cols) { +int getWindowSize(int* screen_rows, int* screen_cols) { struct winsize ws; // Getting window size thanks to ioctl into the given @@ -183,12 +208,107 @@ int getWindowSize(int* rows, int* cols) { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { return -1; } else { - *cols = ws.ws_col; - *rows = ws.ws_row; + *screen_cols = ws.ws_col; + *screen_rows = ws.ws_row; return 0; } } +/*** Row operations ***/ + +int editorRowCursorXToRenderX(editor_row* row, int cursor_x) { + int render_x = 0; + int j; + // For each character, if it’s a tab we use rx % TTE_TAB_STOP + // to find out how many columns we are to the right of the last + // tab stop, and then subtract that from TTE_TAB_STOP - 1 to + // find out how many columns we are to the left of the next tab + // stop. We add that amount to rx to get just to the left of the + // next tab stop, and then the unconditional rx++ statement gets + // us right on the next tab stop. Notice how this works even if + // we are currently on a tab stop. + for (j = 0; j < cursor_x; j++) { + if (row -> chars[j] == '\t') + render_x += (TTE_TAB_STOP - 1) - (render_x % TTE_TAB_STOP); + render_x++; + } + return render_x; +} + +void editorUpdateRow(editor_row* row) { + // First, we have to loop through the chars of the row + // and count the tabs in order to know how much memory + // to allocate for render. The maximum number of characters + // needed for each tab is 8. row->size already counts 1 for + // each tab, so we multiply the number of tabs by 7 and add + // that to row->size to get the maximum amount of memory we’ll + // need for the rendered row. + int tabs = 0; + int j; + for (j = 0; j < row -> size; j++) { + if (row -> chars[j] == '\t') + tabs++; + } + free(row -> render); + row -> render = malloc(row -> size + tabs * (TTE_TAB_STOP - 1) + 1); + + // After allocating the memory, we check whether the current character + // is a tab. If it is, we append one space (because each tab must + // advance the cursor forward at least one column), and then append + // spaces until we get to a tab stop, which is a column that is + // divisible by 8 + int idx = 0; + for (j = 0; j < row -> size; j++) { + if (row -> chars[j] == '\t') { + row -> render[idx++] = ' '; + while (idx % TTE_TAB_STOP != 0) + row -> render[idx++] = ' '; + } else + row -> render[idx++] = row -> chars[j]; + } + row -> render[idx] = '\0'; + row -> render_size = idx; +} + +void editorAppendRow(char* s, size_t line_len) { + ec.row = realloc(ec.row, sizeof(editor_row) * (ec.num_rows + 1)); + + int at = ec.num_rows; + ec.row[at].size = line_len; + ec.row[at].chars = malloc(line_len + 1); // We want to add terminator char '\0' at the end + memcpy(ec.row[at].chars, s, line_len); + ec.row[at].chars[line_len] = '\0'; + + ec.row[at].render_size = 0; + ec.row[at].render = NULL; + editorUpdateRow(&ec.row[at]); + + ec.num_rows++; +} + +/*** File I/O ***/ + +void editorOpen(char* file_name) { + FILE* file = fopen(file_name, "r"); + if (!file) + die("Failed to open the file"); + + char* line = NULL; + // Unsigned int of at least 16 bit. + size_t line_cap = 0; + // Bigger than int + ssize_t line_len; + while ((line_len = getline(&line, &line_cap, file)) != -1) { + // We already know each row represents one line of text, there's no need + // to keep carriage return and newline characters. + if (line_len > 0 && (line[line_len - 1] == '\n' || line[line_len - 1] == '\r')) + line_len--; + editorAppendRow(line, line_len); + } + free(line); + fclose(file); +} + /*** Append buffer section **/ void abufAppend(struct a_buf* ab, const char* s, int len) { @@ -214,16 +334,37 @@ void abufFree(struct a_buf* ab) { /*** Output section ***/ +void editorScroll() { + ec.render_x = 0; + if (ec.cursor_y < ec.num_rows) + ec.render_x = editorRowCursorXToRenderX(&ec.row[ec.cursor_y], ec.cursor_x); + // The first if statement checks if the cursor is above the visible window, + // and if so, scrolls up to where the cursor is. The second if statement checks + // if the cursor is past the bottom of the visible window, and contains slightly + // more complicated arithmetic because ec.row_offset refers to what’s at the top + // of the screen, and we have to get ec.screen_rows involved to talk about what’s + // at the bottom of the screen. + if (ec.cursor_y < ec.row_offset) + ec.row_offset = ec.cursor_y; + if (ec.cursor_y >= ec.row_offset + ec.screen_rows) + ec.row_offset = ec.cursor_y - ec.screen_rows + 1; + + if (ec.render_x < ec.col_offset) + ec.col_offset = ec.render_x; + if (ec.render_x >= ec.col_offset + ec.screen_cols) + ec.col_offset = ec.render_x - ec.screen_cols + 1; +} + void editorDrawWelcomeMessage(struct a_buf* ab) { char welcome[80]; // Using snprintf to truncate message in case the terminal // is too tiny to handle the entire string. int welcome_len = snprintf(welcome, sizeof(welcome), "tte -- version %s", TTE_VERSION); - if (welcome_len > ec.cols) - welcome_len = ec.cols; + if (welcome_len > ec.screen_cols) + welcome_len = ec.screen_cols; // Centering the message. - int padding = (ec.cols - welcome_len) / 2; + int padding = (ec.screen_cols - welcome_len) / 2; // Remember that everything != 0 is true. if (padding) { abufAppend(ab, "~", 1); @@ -236,22 +377,37 @@ void editorDrawWelcomeMessage(struct a_buf* ab) { void editorDrawRows(struct a_buf* ab) { int y; - for (y = 0; y < ec.rows; y++) { - if (y == ec.rows / 3) - editorDrawWelcomeMessage(ab); - else - abufAppend(ab, "~", 1); + for (y = 0; y < ec.screen_rows; y++) { + int file_row = y + ec.row_offset; + if(file_row >= ec.num_rows) { + if (ec.num_rows == 0 && y == ec.screen_rows / 3) + editorDrawWelcomeMessage(ab); + else + abufAppend(ab, "~", 1); + } else { + int len = ec.row[file_row].render_size - ec.col_offset; + // len can be a negative number, meaning the user scrolled + // horizontally past the end of the line. In that case, we set + // len to 0 so that nothing is displayed on that line. + if (len < 0) + len = 0; + if (len > ec.screen_cols) + len = ec.screen_cols; + abufAppend(ab, &ec.row[file_row].render[ec.col_offset], len); + } // Redrawing each line instead of the whole screen. abufAppend(ab, "\x1b[K", 3); // Addind a new line only if we are not printing the // last tilde. - if (y < ec.rows - 1) + if (y < ec.screen_rows - 1) abufAppend(ab, "\r\n", 2); } } void editorRefreshScreen() { + editorScroll(); + struct a_buf ab = ABUF_INIT; // Hiding the cursor while the screen is refreshing. @@ -264,7 +420,7 @@ void editorRefreshScreen() { // Moving the cursor where it should be. char buf[32]; - snprintf(buf, sizeof(buf), "\x1b[%d;%dH", ec.cursor_y + 1, ec.cursor_x + 1); + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (ec.cursor_y - ec.row_offset) + 1, (ec.render_x - ec.col_offset) + 1); abufAppend(&ab, buf, strlen(buf)); // Showing again the cursor. @@ -292,24 +448,42 @@ void editorClearScreen() { /*** Input section ***/ void editorMoveCursor(int key) { + editor_row* row = (ec.cursor_y >= ec.num_rows) ? NULL : &ec.row[ec.cursor_y]; + switch (key) { case ARROW_LEFT: if (ec.cursor_x != 0) ec.cursor_x--; + // If <- is pressed, move to the end of the previous line + else if (ec.cursor_y > 0) { + ec.cursor_y--; + ec.cursor_x = ec.row[ec.cursor_y].size; + } break; case ARROW_RIGHT: - if (ec.cursor_x != ec.cols - 1) + if (row && ec.cursor_x < row -> size) ec.cursor_x++; + // If -> is pressed, move to the start of the next line + else if (row && ec.cursor_x == row -> size) { + ec.cursor_y++; + ec.cursor_x = 0; + } break; case ARROW_UP: if (ec.cursor_y != 0) ec.cursor_y--; break; case ARROW_DOWN: - if (ec.cursor_y != ec.rows - 1) + if (ec.cursor_y < ec.num_rows) ec.cursor_y++; break; } + + // Move cursor_x if it ends up past the end of the line it's on + row = (ec.cursor_y >= ec.num_rows) ? NULL : &ec.row[ec.cursor_y]; + int row_len = row ? row -> size : 0; + if (ec.cursor_x > row_len) + ec.cursor_x = row_len; } void editorProcessKeypress() { @@ -329,7 +503,12 @@ void editorProcessKeypress() { case PAGE_UP: case PAGE_DOWN: { // You can't declare variables directly inside a switch statement. - int times = ec.rows; + if (c == PAGE_UP) + ec.cursor_y = ec.row_offset; + else if (c == PAGE_DOWN) + ec.cursor_y = ec.row_offset + ec.screen_rows - 1; + + int times = ec.screen_rows; while (times--) editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); } @@ -338,7 +517,7 @@ void editorProcessKeypress() { ec.cursor_x = 0; break; case END_KEY: - ec.cursor_x = ec.cols - 1; + ec.cursor_x = ec.screen_cols - 1; break; } } @@ -348,14 +527,21 @@ void editorProcessKeypress() { void initEditor() { ec.cursor_x = 0; ec.cursor_y = 0; + ec.render_x = 0; + ec.row_offset = 0; + ec.col_offset = 0; + ec.num_rows = 0; + ec.row = NULL; - if (getWindowSize(&ec.rows, &ec.cols) == -1) + if (getWindowSize(&ec.screen_rows, &ec.screen_cols) == -1) die("Failed to get window size"); } -int main() { +int main(int argc, char* argv[]) { enableRawMode(); initEditor(); + if (argc >= 2) + editorOpen(argv[1]); while (1) { editorRefreshScreen();