You can now open files and. Also scrolling is implemented.
This commit is contained in:
@@ -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 <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user