You can now open files and. Also scrolling is implemented.
This commit is contained in:
1 parent
ce1bf87767
commit
a733fc40c5
1 file changed
+208
-22
@@ -1,11 +1,22 @@
|
|||||||
/*** Include section ***/
|
/*** 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 <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -18,14 +29,28 @@
|
|||||||
#define ABUF_INIT {NULL, 0}
|
#define ABUF_INIT {NULL, 0}
|
||||||
// Version code
|
// Version code
|
||||||
#define TTE_VERSION "0.0.1"
|
#define TTE_VERSION "0.0.1"
|
||||||
|
// Length of a tab stop
|
||||||
|
#define TTE_TAB_STOP 8
|
||||||
|
|
||||||
/*** Data section ***/
|
/*** Data section ***/
|
||||||
|
|
||||||
|
typedef struct editor_row {
|
||||||
|
int size;
|
||||||
|
int render_size;
|
||||||
|
char* chars;
|
||||||
|
char* render;
|
||||||
|
} editor_row;
|
||||||
|
|
||||||
struct editor_config {
|
struct editor_config {
|
||||||
int cursor_x;
|
int cursor_x;
|
||||||
int cursor_y;
|
int cursor_y;
|
||||||
int rows;
|
int render_x;
|
||||||
int cols;
|
int row_offset;
|
||||||
|
int col_offset;
|
||||||
|
int screen_rows;
|
||||||
|
int screen_cols;
|
||||||
|
int num_rows;
|
||||||
|
editor_row* row;
|
||||||
struct termios orig_termios;
|
struct termios orig_termios;
|
||||||
} ec;
|
} ec;
|
||||||
|
|
||||||
@@ -38,7 +63,7 @@ struct a_buf {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum editor_key {
|
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_RIGHT,
|
||||||
ARROW_UP,
|
ARROW_UP,
|
||||||
ARROW_DOWN,
|
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;
|
struct winsize ws;
|
||||||
|
|
||||||
// Getting window size thanks to ioctl into the given
|
// 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) {
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
*cols = ws.ws_col;
|
*screen_cols = ws.ws_col;
|
||||||
*rows = ws.ws_row;
|
*screen_rows = ws.ws_row;
|
||||||
return 0;
|
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 **/
|
/*** Append buffer section **/
|
||||||
|
|
||||||
void abufAppend(struct a_buf* ab, const char* s, int len) {
|
void abufAppend(struct a_buf* ab, const char* s, int len) {
|
||||||
@@ -214,16 +334,37 @@ void abufFree(struct a_buf* ab) {
|
|||||||
|
|
||||||
/*** Output section ***/
|
/*** 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) {
|
void editorDrawWelcomeMessage(struct a_buf* ab) {
|
||||||
char welcome[80];
|
char welcome[80];
|
||||||
// Using snprintf to truncate message in case the terminal
|
// Using snprintf to truncate message in case the terminal
|
||||||
// is too tiny to handle the entire string.
|
// is too tiny to handle the entire string.
|
||||||
int welcome_len = snprintf(welcome, sizeof(welcome),
|
int welcome_len = snprintf(welcome, sizeof(welcome),
|
||||||
"tte -- version %s", TTE_VERSION);
|
"tte -- version %s", TTE_VERSION);
|
||||||
if (welcome_len > ec.cols)
|
if (welcome_len > ec.screen_cols)
|
||||||
welcome_len = ec.cols;
|
welcome_len = ec.screen_cols;
|
||||||
// Centering the message.
|
// Centering the message.
|
||||||
int padding = (ec.cols - welcome_len) / 2;
|
int padding = (ec.screen_cols - welcome_len) / 2;
|
||||||
// Remember that everything != 0 is true.
|
// Remember that everything != 0 is true.
|
||||||
if (padding) {
|
if (padding) {
|
||||||
abufAppend(ab, "~", 1);
|
abufAppend(ab, "~", 1);
|
||||||
@@ -236,22 +377,37 @@ void editorDrawWelcomeMessage(struct a_buf* ab) {
|
|||||||
|
|
||||||
void editorDrawRows(struct a_buf* ab) {
|
void editorDrawRows(struct a_buf* ab) {
|
||||||
int y;
|
int y;
|
||||||
for (y = 0; y < ec.rows; y++) {
|
for (y = 0; y < ec.screen_rows; y++) {
|
||||||
if (y == ec.rows / 3)
|
int file_row = y + ec.row_offset;
|
||||||
editorDrawWelcomeMessage(ab);
|
if(file_row >= ec.num_rows) {
|
||||||
else
|
if (ec.num_rows == 0 && y == ec.screen_rows / 3)
|
||||||
abufAppend(ab, "~", 1);
|
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.
|
// Redrawing each line instead of the whole screen.
|
||||||
abufAppend(ab, "\x1b[K", 3);
|
abufAppend(ab, "\x1b[K", 3);
|
||||||
// Addind a new line only if we are not printing the
|
// Addind a new line only if we are not printing the
|
||||||
// last tilde.
|
// last tilde.
|
||||||
if (y < ec.rows - 1)
|
if (y < ec.screen_rows - 1)
|
||||||
abufAppend(ab, "\r\n", 2);
|
abufAppend(ab, "\r\n", 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void editorRefreshScreen() {
|
void editorRefreshScreen() {
|
||||||
|
editorScroll();
|
||||||
|
|
||||||
struct a_buf ab = ABUF_INIT;
|
struct a_buf ab = ABUF_INIT;
|
||||||
|
|
||||||
// Hiding the cursor while the screen is refreshing.
|
// Hiding the cursor while the screen is refreshing.
|
||||||
@@ -264,7 +420,7 @@ void editorRefreshScreen() {
|
|||||||
|
|
||||||
// Moving the cursor where it should be.
|
// Moving the cursor where it should be.
|
||||||
char buf[32];
|
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));
|
abufAppend(&ab, buf, strlen(buf));
|
||||||
|
|
||||||
// Showing again the cursor.
|
// Showing again the cursor.
|
||||||
@@ -292,24 +448,42 @@ void editorClearScreen() {
|
|||||||
/*** Input section ***/
|
/*** Input section ***/
|
||||||
|
|
||||||
void editorMoveCursor(int key) {
|
void editorMoveCursor(int key) {
|
||||||
|
editor_row* row = (ec.cursor_y >= ec.num_rows) ? NULL : &ec.row[ec.cursor_y];
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case ARROW_LEFT:
|
case ARROW_LEFT:
|
||||||
if (ec.cursor_x != 0)
|
if (ec.cursor_x != 0)
|
||||||
ec.cursor_x--;
|
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;
|
break;
|
||||||
case ARROW_RIGHT:
|
case ARROW_RIGHT:
|
||||||
if (ec.cursor_x != ec.cols - 1)
|
if (row && ec.cursor_x < row -> size)
|
||||||
ec.cursor_x++;
|
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;
|
break;
|
||||||
case ARROW_UP:
|
case ARROW_UP:
|
||||||
if (ec.cursor_y != 0)
|
if (ec.cursor_y != 0)
|
||||||
ec.cursor_y--;
|
ec.cursor_y--;
|
||||||
break;
|
break;
|
||||||
case ARROW_DOWN:
|
case ARROW_DOWN:
|
||||||
if (ec.cursor_y != ec.rows - 1)
|
if (ec.cursor_y < ec.num_rows)
|
||||||
ec.cursor_y++;
|
ec.cursor_y++;
|
||||||
break;
|
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() {
|
void editorProcessKeypress() {
|
||||||
@@ -329,7 +503,12 @@ void editorProcessKeypress() {
|
|||||||
case PAGE_UP:
|
case PAGE_UP:
|
||||||
case PAGE_DOWN:
|
case PAGE_DOWN:
|
||||||
{ // You can't declare variables directly inside a switch statement.
|
{ // 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--)
|
while (times--)
|
||||||
editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
|
editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
|
||||||
}
|
}
|
||||||
@@ -338,7 +517,7 @@ void editorProcessKeypress() {
|
|||||||
ec.cursor_x = 0;
|
ec.cursor_x = 0;
|
||||||
break;
|
break;
|
||||||
case END_KEY:
|
case END_KEY:
|
||||||
ec.cursor_x = ec.cols - 1;
|
ec.cursor_x = ec.screen_cols - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,14 +527,21 @@ void editorProcessKeypress() {
|
|||||||
void initEditor() {
|
void initEditor() {
|
||||||
ec.cursor_x = 0;
|
ec.cursor_x = 0;
|
||||||
ec.cursor_y = 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");
|
die("Failed to get window size");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main(int argc, char* argv[]) {
|
||||||
enableRawMode();
|
enableRawMode();
|
||||||
initEditor();
|
initEditor();
|
||||||
|
if (argc >= 2)
|
||||||
|
editorOpen(argv[1]);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
editorRefreshScreen();
|
editorRefreshScreen();
|
||||||
|
|||||||
Reference in new issue
Block a user