summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile14
-rw-r--r--TODO2
-rw-r--r--buffer.c21
-rw-r--r--buffer.h11
-rw-r--r--editor.c361
-rw-r--r--editor.h35
-rw-r--r--file.c94
-rw-r--r--file.h6
-rw-r--r--input.c169
-rw-r--r--input.h22
-rw-r--r--main.c25
-rw-r--r--row.c149
-rw-r--r--row.h27
-rw-r--r--syntax.c197
-rw-r--r--syntax.h36
-rw-r--r--terminal.c84
-rw-r--r--terminal.h7
17 files changed, 1260 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2f785c9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+CFLAGS=-std=c99
+
+OUT=editor
+SRC=main.c \
+ buffer.c \
+ editor.c \
+ file.c \
+ input.c \
+ row.c \
+ syntax.c \
+ terminal.c \
+
+${OUT}: ${SRC}
+ ${CC} ${CFLAGS} ${SRC} -o ${OUT}
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..460325d
--- /dev/null
+++ b/TODO
@@ -0,0 +1,2 @@
+- Write comments in some of the messy code
+- Try and keep editor_state out of row.c
diff --git a/buffer.c b/buffer.c
new file mode 100644
index 0000000..596d5d2
--- /dev/null
+++ b/buffer.c
@@ -0,0 +1,21 @@
+#include "buffer.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+void ab_append(struct append_buffer* ab, const char* string, int length)
+{
+ char* new = realloc(ab->buffer, ab->length + length);
+
+ if (new == NULL)
+ return;
+
+ memcpy(&new[ab->length], string, length);
+ ab->buffer = new;
+ ab->length += length;
+}
+
+void ab_free(struct append_buffer* ab)
+{
+ free(ab->buffer);
+}
diff --git a/buffer.h b/buffer.h
new file mode 100644
index 0000000..474202e
--- /dev/null
+++ b/buffer.h
@@ -0,0 +1,11 @@
+#pragma once
+
+struct append_buffer {
+ char* buffer;
+ int length;
+};
+
+#define ABUF_INIT { NULL, 0 }
+
+void ab_append(struct append_buffer* ab, const char* string, int length);
+void ab_free(struct append_buffer* ab);
diff --git a/editor.c b/editor.c
new file mode 100644
index 0000000..14eada7
--- /dev/null
+++ b/editor.c
@@ -0,0 +1,361 @@
+#include "editor.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "buffer.h"
+#include "input.h"
+#include "syntax.h"
+#include "terminal.h"
+#include "row.h"
+
+void init_editor(struct editor_state* editor)
+{
+ editor->cursor_x = 0;
+ editor->cursor_y = 0;
+ editor->cursor_display_x = 0;
+ editor->row_offset = 0;
+ editor->col_offset = 0;
+ editor->row_count = 0;
+ editor->rows = NULL;
+ editor->dirty = 0;
+ editor->filename = NULL;
+ editor->status_message[0] = '\0';
+ editor->status_message_time = 0;
+ editor->syntax = NULL;
+
+ if (get_window_size(&editor->screen_rows, &editor->screen_cols) == -1)
+ die("get_window_size");
+
+ editor->screen_rows -= 2;
+}
+
+void editor_set_status_message(struct editor_state* editor, const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vsnprintf(editor->status_message, sizeof(editor->status_message), format, args);
+ va_end(args);
+ editor->status_message_time = time(NULL);
+}
+
+void editor_refresh_screen(struct editor_state* editor)
+{
+ editor_scroll(editor);
+
+ struct append_buffer buffer = ABUF_INIT;
+
+ ab_append(&buffer, "\x1b[?25l", 6);
+ ab_append(&buffer, "\x1b[H", 3);
+
+ editor_draw_rows(editor, &buffer);
+ editor_draw_status_bar(editor, &buffer);
+ editor_draw_message_bar(editor, &buffer);
+
+ char buf[32];
+ snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (editor->cursor_y - editor->row_offset) + 1, (editor->cursor_display_x - editor->col_offset) + 1);
+ ab_append(&buffer, buf, strlen(buf));
+
+ ab_append(&buffer, "\x1b[?25h", 6);
+
+ write(STDOUT_FILENO, buffer.buffer, buffer.length);
+ ab_free(&buffer);
+}
+
+char* editor_prompt(struct editor_state* editor, char* prompt, void (*callback)(struct editor_state*, char*, int))
+{
+ size_t buffer_size = 128;
+ char* buffer = malloc(buffer_size);
+
+ size_t buffer_length = 0;
+ buffer[0] = '\0';
+
+ while (1) {
+ editor_set_status_message(editor, prompt, buffer);
+ // TODO: This should refresh the active screen
+ //editor_refresh_screen();
+
+ int c = editor_read_key();
+ if (c == DELETE_KEY || c == BACKSPACE) {
+ if (buffer_length != 0) buffer[--buffer_length] = '\0';
+ } else if (c == '\x1b') {
+ editor_set_status_message(editor, "");
+ if (callback) callback(editor, buffer, c);
+ free(buffer);
+ return NULL;
+ } else if (c == '\r') {
+ if (buffer_length != 0) {
+ editor_set_status_message(editor, "");
+ if (callback) callback(editor, buffer, c);
+ return buffer;
+ }
+ } else if (!iscntrl(c) && c < 128) {
+ if (buffer_length == buffer_size - 1) {
+ buffer_size *= 2;
+ buffer = realloc(buffer, buffer_size);
+ }
+ buffer[buffer_length++] = c;
+ buffer[buffer_length] = '\0';
+ }
+ if (callback) callback(editor, buffer, c);
+ }
+}
+
+void editor_insert_char(struct editor_state* editor, int c)
+{
+ if (editor->cursor_y == editor->row_count)
+ insert_row(editor, editor->row_count, "", 0);
+
+ row_insert_char(editor, &editor->rows[editor->cursor_y], editor->cursor_x, c);
+ editor->cursor_x++;
+}
+
+void editor_insert_newline(struct editor_state* editor)
+{
+ if (editor->cursor_x == 0) {
+ insert_row(editor, editor->cursor_y, "", 0);
+ } else {
+ struct editor_row* row = &editor->rows[editor->cursor_y];
+ insert_row(editor, editor->cursor_y + 1, &row->chars[editor->cursor_x], row->size - editor->cursor_x);
+ row = &editor->rows[editor->cursor_y];
+ row->size = editor->cursor_x;
+ row->chars[row->size] = '\0';
+ update_row(editor, row);
+ }
+ editor->cursor_y++;
+ editor->cursor_x = 0;
+}
+
+void editor_delete_char(struct editor_state* editor)
+{
+ if (editor->cursor_y == editor->row_count)
+ return;
+
+ if (editor->cursor_x == 0 && editor->cursor_y == 0)
+ return;
+
+ struct editor_row* row = &editor->rows[editor->cursor_y];
+ if (editor->cursor_x > 0) {
+ row_delete_char(editor, row, editor->cursor_x - 1);
+ editor->cursor_x--;
+ } else {
+ editor->cursor_x = editor->rows[editor->cursor_y - 1].size;
+ row_append_string(editor, &editor->rows[editor->cursor_y - 1], row->chars, row->size);
+ delete_row(editor, editor->cursor_y);
+ editor->cursor_y--;
+ }
+}
+
+static void editor_find_callback(struct editor_state* editor, char* query, int key)
+{
+ static int last_match = -1;
+ static int direction = 1;
+
+ static int saved_highlight_line;
+ static char* saved_highlight;
+
+ if (saved_highlight) {
+ memset(editor->rows[saved_highlight_line].highlight, (size_t)saved_highlight, editor->rows[saved_highlight_line].render_size);
+ free(saved_highlight);
+ saved_highlight = NULL;
+ }
+
+ if (key == '\r' || key == '\x1b') {
+ last_match = -1;
+ direction = 1;
+ return;
+ } else if (key == ARROW_RIGHT || key == ARROW_DOWN) {
+ direction = 1;
+ } else if (key == ARROW_LEFT || key == ARROW_UP) {
+ direction = -1;
+ } else {
+ last_match = -1;
+ direction = 1;
+ }
+
+ if (last_match == -1)
+ direction = 1;
+ int current = last_match;
+
+ int i;
+ for (i = 0; i < editor->row_count; i++) {
+ current += direction;
+ if (current == -1) {
+ current = editor->row_count - 1;
+ } else if (current == editor->row_count) {
+ current = 0;
+ }
+
+ struct editor_row* row = &editor->rows[current];
+ char* match = strstr(row->render, query);
+
+ if (match) {
+ last_match = current;
+ editor->cursor_y = current;
+ editor->cursor_x = row_display_x_to_x(row, match - row->render);
+ editor->row_offset = editor->row_count;
+
+ saved_highlight_line = current;
+ saved_highlight = malloc(row->render_size);
+ memcpy(saved_highlight, row->highlight, row->render_size);
+ memset(&row->highlight[match - row->render], HIGHLIGHT_MATCH, strlen(query));
+ break;
+ }
+ }
+}
+
+void editor_find(struct editor_state* editor)
+{
+ int saved_cursor_x = editor->cursor_x;
+ int saved_cursor_y = editor->cursor_y;
+ int saved_col_offset = editor->col_offset;
+ int saved_row_offset = editor->row_offset;
+
+ char* query = editor_prompt(editor, "Search: %s (Use Esc/Arrows/Enter)", editor_find_callback);
+ if (query) {
+ free(query);
+ } else {
+ editor->cursor_x = saved_cursor_x;
+ editor->cursor_y = saved_cursor_y;
+ editor->col_offset = saved_col_offset;
+ editor->row_offset = saved_row_offset;
+ }
+}
+
+void editor_scroll(struct editor_state* editor)
+{
+ editor->cursor_display_x = 0;
+ if (editor->cursor_y < editor->row_count)
+ editor->cursor_display_x = row_x_to_display_x(&editor->rows[editor->cursor_y], editor->cursor_x);
+
+ if (editor->cursor_y < editor->row_offset)
+ editor->row_offset = editor->cursor_y;
+
+ if (editor->cursor_y >= editor->row_offset + editor->screen_rows)
+ editor->row_offset = editor->cursor_y - editor->screen_rows + 1;
+
+ if (editor->cursor_display_x < editor->col_offset)
+ editor->col_offset = editor->cursor_display_x;
+
+ if (editor->cursor_display_x >= editor->col_offset + editor->screen_cols)
+ editor->col_offset = editor->cursor_display_x - editor->screen_cols + 1;
+}
+
+void editor_draw_rows(struct editor_state* editor, struct append_buffer* buffer)
+{
+ int y;
+ for (y = 0; y < editor->screen_rows; y++) {
+ int file_row = y + editor->row_offset;
+ if (file_row >= editor->row_count) {
+ if (editor->row_count == 0 && y == editor->screen_rows / 3) {
+ char welcome[80];
+ int welcome_length = snprintf(welcome, sizeof(welcome), "Welcome to cflip text editor");
+
+ if (welcome_length > editor->screen_cols)
+ welcome_length = editor->screen_cols;
+
+ int padding = (editor->screen_cols - welcome_length) / 2;
+ if (padding) {
+ ab_append(buffer, "~", 1);
+ padding--;
+ }
+
+ while (padding--)
+ ab_append(buffer, " ", 1);
+
+ ab_append(buffer, welcome, welcome_length);
+ } else {
+ ab_append(buffer, "~", 1);
+ }
+ } else {
+ int length = editor->rows[file_row].render_size - editor->col_offset;
+
+ if (length < 0) length = 0;
+ if (length > editor->screen_cols) length = editor->screen_cols;
+
+ char* c = &editor->rows[file_row].render[editor->col_offset];
+ unsigned char* highlight = &editor->rows[file_row].highlight[editor->col_offset];
+ int current_colour = -1;
+ int j;
+
+ for (j = 0; j < length; j++) {
+ if (iscntrl(c[j])) {
+ char symbol = (c[j] <= 26) ? '@' + c[j] : '?';
+ ab_append(buffer, "\x1b[7m", 4);
+ ab_append(buffer, &symbol, 1);
+ ab_append(buffer, "\x1b[m", 3);
+
+ if (current_colour != -1) {
+ char buf[16];
+ int col_length = snprintf(buf, sizeof(buf), "\x1b[%dm", current_colour);
+ ab_append(buffer, buf, col_length);
+ }
+ } else if (highlight[j] == HIGHLIGHT_NORMAL) {
+ if (current_colour != -1) {
+ ab_append(buffer, "\x1b[39m", 5);
+ current_colour = -1;
+ }
+ ab_append(buffer, &c[j], 1);
+ } else {
+ int colour = editor_syntax_to_colour(highlight[j]);
+ if (colour != current_colour) {
+ current_colour = colour;
+ char colour_buffer[16];
+ int colour_buffer_length = snprintf(colour_buffer, sizeof(colour_buffer), "\x1b[%dm", colour);
+
+ ab_append(buffer, colour_buffer, colour_buffer_length);
+ }
+ ab_append(buffer, &c[j], 1);
+ }
+ }
+ ab_append(buffer, "\x1b[39m", 5);
+ }
+
+ ab_append(buffer, "\x1b[K", 3);
+ ab_append(buffer, "\r\n", 2);
+ }
+}
+
+void editor_draw_status_bar(struct editor_state* editor, struct append_buffer* buffer)
+{
+ ab_append(buffer, "\x1b[7m", 4);
+
+ char status[80], right_status[80];
+ int length = snprintf(status, sizeof(status), "%.20s - %d lines %s", editor->filename ? editor->filename : "[New File]", editor->row_count, editor->dirty ? "(modified)" : "");
+ int right_length = snprintf(right_status, sizeof(right_status), "%s | %d/%d", editor->syntax ? editor->syntax->filetype : "plaintext", editor->cursor_y + 1, editor->row_count);
+
+ if (length > editor->screen_cols)
+ length = editor->screen_cols;
+
+ ab_append(buffer, status, length);
+
+ while (length < editor->screen_cols) {
+ if (editor->screen_cols - length == right_length) {
+ ab_append(buffer, right_status, right_length);
+ break;
+ } else {
+ ab_append(buffer, " ", 1);
+ length++;
+ }
+ }
+
+ ab_append(buffer, "\x1b[m", 3);
+ ab_append(buffer, "\r\n", 2);
+}
+
+void editor_draw_message_bar(struct editor_state* editor, struct append_buffer* buffer)
+{
+ ab_append(buffer, "\x1b[K", 3);
+
+ int message_length = strlen(editor->status_message);
+
+ if (message_length > editor->screen_cols)
+ message_length = editor->screen_cols;
+
+ if (message_length && time(NULL) - editor->status_message_time < 5)
+ ab_append(buffer, editor->status_message, message_length);
+}
diff --git a/editor.h b/editor.h
new file mode 100644
index 0000000..578a91e
--- /dev/null
+++ b/editor.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <time.h>
+#include "buffer.h"
+
+struct editor_state {
+ int cursor_x, cursor_y;
+ int cursor_display_x;
+ int row_offset;
+ int col_offset;
+ int screen_rows;
+ int screen_cols;
+ int row_count;
+ struct editor_row* rows;
+ int dirty;
+ char* filename;
+ char status_message[80];
+ time_t status_message_time;
+ struct editor_syntax* syntax;
+};
+
+void init_editor(struct editor_state* editor);
+
+void editor_set_status_message(struct editor_state* editor, const char* format, ...);
+void editor_refresh_screen(struct editor_state* editor);
+char* editor_prompt(struct editor_state* editor, char* prompt, void (*callback)(struct editor_state*, char*, int));
+
+void editor_insert_char(struct editor_state* editor, int c);
+void editor_insert_newline(struct editor_state* editor);
+void editor_delete_char(struct editor_state* editor);
+void editor_find(struct editor_state* editor);
+void editor_scroll(struct editor_state* editor);
+void editor_draw_rows(struct editor_state* editor, struct append_buffer* buffer);
+void editor_draw_status_bar(struct editor_state* editor, struct append_buffer* buffer);
+void editor_draw_message_bar(struct editor_state* editor, struct append_buffer* buffer);
diff --git a/file.c b/file.c
new file mode 100644
index 0000000..78505df
--- /dev/null
+++ b/file.c
@@ -0,0 +1,94 @@
+#include "file.h"
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "row.h"
+#include "syntax.h"
+#include "terminal.h"
+
+char* editor_rows_to_string(struct editor_state* editor, int* buffer_length)
+{
+ int total_length = 0;
+ int j;
+
+ for (j = 0; j < editor->row_count; j++)
+ total_length += editor->rows[j].size + 1;
+
+ *buffer_length = total_length;
+
+ char* buffer = malloc(total_length);
+ char* p = buffer;
+
+ for (j = 0; j < editor->row_count; j++) {
+ memcpy(p, editor->rows[j].chars, editor->rows[j].size);
+ p += editor->rows[j].size;
+ *p = '\n';
+ p++;
+ }
+
+ return buffer;
+}
+
+void editor_open(struct editor_state* editor, char* filename)
+{
+ free(editor->filename);
+ editor->filename = strdup(filename);
+
+ editor_select_syntax_highlight(editor);
+
+ FILE* fp = fopen(filename, "r");
+ if (!fp)
+ die("fopen");
+
+ char* line = NULL;
+ size_t line_capacity = 0;
+ ssize_t line_length;
+
+ while ((line_length = getline(&line, &line_capacity, fp)) != -1) {
+ while (line_length > 0 && (line[line_length - 1] == '\n' || line[line_length - 1] == '\r'))
+ line_length--;
+
+ insert_row(editor, editor->row_count, line, line_length);
+ }
+
+ free(line);
+ fclose(fp);
+
+ editor->dirty = 0;
+}
+
+void editor_save(struct editor_state* editor)
+{
+ if (editor->filename == NULL) {
+ editor->filename = editor_prompt(editor, "Save as: %s", NULL);
+
+ if (editor->filename == NULL)
+ return;
+
+ editor_select_syntax_highlight(editor);
+ }
+
+ int length;
+ char* buffer = editor_rows_to_string(editor, &length);
+
+ int fd = open(editor->filename, O_RDWR | O_CREAT, 0644);
+ if (fd != -1) {
+ if (ftruncate(fd, length) != -1) {
+ if (write(fd, buffer, length) == length) {
+ close(fd);
+ free(buffer);
+ editor_set_status_message(editor, "%d bytes written to disk", length);
+ editor->dirty = 0;
+ return;
+ }
+ }
+ close(fd);
+ }
+ free(buffer);
+ editor_set_status_message(editor, "Failed to write to disk: %s", strerror(errno));
+}
diff --git a/file.h b/file.h
new file mode 100644
index 0000000..831d193
--- /dev/null
+++ b/file.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "editor.h"
+
+void editor_open(struct editor_state* editor, char* filename);
+void editor_save(struct editor_state* editor);
diff --git a/input.c b/input.c
new file mode 100644
index 0000000..17a37f6
--- /dev/null
+++ b/input.c
@@ -0,0 +1,169 @@
+#include "input.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "file.h"
+#include "terminal.h"
+#include "row.h"
+
+int editor_read_key()
+{
+ int read_count;
+ char c;
+
+ while ((read_count = read(STDIN_FILENO, &c, 1)) != 1) {
+ if (read_count == -1 && errno != EAGAIN)
+ die("read");
+ }
+
+ if (c == '\x1b') {
+ char seq[3];
+
+ if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b';
+ if (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 HOME_KEY;
+ case '3': return DELETE_KEY;
+ case '4': return END_KEY;
+ case '5': return PAGE_UP;
+ case '6': return PAGE_DOWN;
+ case '7': return HOME_KEY;
+ case '8': return END_KEY;
+ }
+ }
+ } else {
+ switch (seq[1]) {
+ case 'A': return ARROW_UP;
+ case 'B': return ARROW_DOWN;
+ case 'C': return ARROW_RIGHT;
+ case 'D': return ARROW_LEFT;
+ case 'H': return HOME_KEY;
+ case 'F': return END_KEY;
+ }
+ }
+ } else if (seq[0] == 'O') {
+ switch (seq[1]) {
+ case 'H': return HOME_KEY;
+ case 'F': return END_KEY;
+ }
+ }
+ return '\x1b';
+ } else {
+ return c;
+ }
+}
+
+void editor_move_cursor(struct editor_state* editor, int key)
+{
+ struct editor_row* row = (editor->cursor_y >= editor->row_count) ? NULL : &editor->rows[editor->cursor_y];
+
+ switch (key) {
+ case ARROW_LEFT:
+ if (editor->cursor_x != 0) {
+ editor->cursor_x--;
+ } else if (editor->cursor_y > 0) {
+ editor->cursor_y--;
+ editor->cursor_x = editor->rows[editor->cursor_y].size;
+ }
+ break;
+ case ARROW_RIGHT:
+ if (row && editor->cursor_x < row->size) {
+ editor->cursor_x++;
+ } else if (row && editor->cursor_x == row->size) {
+ editor->cursor_y++;
+ editor->cursor_x = 0;
+ }
+ break;
+ case ARROW_UP:
+ if (editor->cursor_y != 0) editor->cursor_y--;
+ break;
+ case ARROW_DOWN:
+ if (editor->cursor_y != editor->row_count - 1) editor->cursor_y++;
+ break;
+ }
+
+ row = (editor->cursor_y >= editor->row_count) ? NULL : &editor->rows[editor->cursor_y];
+ int row_length = row ? row->size : 0;
+ if (editor->cursor_x > row_length)
+ editor->cursor_x = row_length;
+}
+
+void editor_process_keypress(struct editor_state* editor)
+{
+ static int quit_message = 1;
+ int c = editor_read_key();
+
+ switch (c) {
+ case '\r':
+ editor_insert_newline(editor);
+ break;
+ case CTRL_KEY('q'):
+ if (editor->dirty && quit_message) {
+ editor_set_status_message(editor, "This file has unsaved changes. Press Ctrl+Q again to quit");
+ quit_message = 0;
+ return;
+ }
+ write(STDOUT_FILENO, "\x1b[2J", 4);
+ write(STDOUT_FILENO, "\x1b[H", 3);
+ exit(0);
+ break;
+ case CTRL_KEY('s'):
+ editor_save(editor);
+ break;
+ case HOME_KEY:
+ editor->cursor_x = 0;
+ break;
+ case END_KEY:
+ if (editor->cursor_y < editor->row_count)
+ editor->cursor_x = editor->rows[editor->cursor_y].size;
+ break;
+ case CTRL_KEY('f'):
+ editor_find(editor);
+ break;
+ case BACKSPACE:
+ case DELETE_KEY:
+ if (c == DELETE_KEY)
+ editor_move_cursor(editor, ARROW_RIGHT);
+ editor_delete_char(editor);
+ break;
+ case PAGE_UP:
+ case PAGE_DOWN:
+ {
+ if (c == PAGE_UP) {
+ editor->cursor_y = editor->row_offset;
+ } else {
+ editor->cursor_y = editor->row_offset + editor->screen_rows -1;
+ if (editor->cursor_y > editor->row_count)
+ editor->cursor_y = editor->row_count;
+ }
+
+ int times = editor->screen_rows;
+ while (times--)
+ editor_move_cursor(editor, c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
+ }
+ break;
+ case ARROW_UP:
+ case ARROW_DOWN:
+ case ARROW_LEFT:
+ case ARROW_RIGHT:
+ editor_move_cursor(editor, c);
+ break;
+ case CTRL_KEY('l'):
+ case '\x1b':
+ break;
+ default:
+ editor_insert_char(editor, c);
+ break;
+ }
+
+ quit_message = 1;
+}
diff --git a/input.h b/input.h
new file mode 100644
index 0000000..5e63904
--- /dev/null
+++ b/input.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "editor.h"
+
+enum editor_key {
+ BACKSPACE = 127,
+ ARROW_LEFT = 1000,
+ ARROW_RIGHT,
+ ARROW_UP,
+ ARROW_DOWN,
+ DELETE_KEY,
+ HOME_KEY,
+ END_KEY,
+ PAGE_UP,
+ PAGE_DOWN
+};
+
+#define CTRL_KEY(k) ((k) & 0x1f)
+
+int editor_read_key();
+void editor_move_cursor(struct editor_state* editor, int key);
+void editor_process_keypress(struct editor_state* editor);
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..1d17f79
--- /dev/null
+++ b/main.c
@@ -0,0 +1,25 @@
+#include "input.h"
+#include "file.h"
+#include "editor.h"
+#include "terminal.h"
+
+int main(int argc, char** argv)
+{
+ enable_raw_mode();
+
+ struct editor_state editor;
+ init_editor(&editor);
+
+ if (argc >= 2) {
+ editor_open(&editor, argv[1]);
+ }
+
+ editor_set_status_message(&editor, "HELP: Ctrl+Q = quit, Ctrl+S = save, Ctrl+F = find");
+
+ while (1) {
+ editor_refresh_screen(&editor);
+ editor_process_keypress(&editor);
+ }
+
+ return 0;
+}
diff --git a/row.c b/row.c
new file mode 100644
index 0000000..c009cfc
--- /dev/null
+++ b/row.c
@@ -0,0 +1,149 @@
+#include "row.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "syntax.h"
+
+int row_x_to_display_x(struct editor_row* row, int x)
+{
+ int display_x = 0;
+ int j;
+
+ for (j = 0; j < x; j++) {
+ if (row->chars[j] == '\t')
+ display_x += (TAB_WIDTH - 1) - (display_x % TAB_WIDTH);
+ display_x++;
+ }
+
+ return display_x;
+}
+
+int row_display_x_to_x(struct editor_row* row, int display_x)
+{
+ int current_display_x = 0;
+ int result_x;
+
+ for (result_x = 0; result_x < row->size; result_x++) {
+ if (row->chars[result_x] == '\t')
+ result_x += (TAB_WIDTH - 1) - (current_display_x % TAB_WIDTH);
+ result_x++;
+
+ if (current_display_x > display_x)
+ return result_x;
+ }
+ return result_x;
+}
+
+void update_row(struct editor_state* editor, struct editor_row* 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 * (TAB_WIDTH - 1) + 1);
+
+ int index = 0;
+ for (j = 0; j < row->size; j++) {
+ if (row->chars[j] == '\t') {
+ row->render[index++] = ' ';
+ while (index % TAB_WIDTH != 0) row->render[index++] = ' ';
+ } else {
+ row->render[index++] = row->chars[j];
+ }
+ }
+
+ row->render[index] = '\0';
+ row->render_size = index;
+
+ editor_update_syntax(editor, row);
+}
+
+void insert_row(struct editor_state* editor, int at, char* string, size_t length)
+{
+ if (at < 0 || at > editor->row_count)
+ return;
+
+ editor->rows = realloc(editor->rows, sizeof(struct editor_row) * (editor->row_count + 1));
+ memmove(&editor->rows[at + 1], &editor->rows[at], sizeof(struct editor_row) * (editor->row_count - at));
+
+ for (int j = at + 1; j <= editor->row_count; j++)
+ editor->rows[at].index++;
+
+ editor->rows[at].index = at;
+
+ editor->rows[at].size = length;
+ editor->rows[at].chars = malloc(length + 1);
+ memcpy(editor->rows[at].chars, string, length);
+ editor->rows[at].chars[length] = '\0';
+
+ editor->rows[at].render_size = 0;
+ editor->rows[at].render = NULL;
+ editor->rows[at].highlight = NULL;
+ editor->rows[at].highlight_open_comment = 0;
+ update_row(editor, &editor->rows[at]);
+
+ editor->row_count++;
+ editor->dirty = 1;
+}
+
+void free_row(struct editor_row* row)
+{
+ free(row->render);
+ free(row->chars);
+ free(row->highlight);
+}
+
+void delete_row(struct editor_state* editor, int at)
+{
+ if (at < 0 || at >= editor->row_count)
+ return;
+
+ free_row(&editor->rows[at]);
+ memmove(&editor->rows[at], &editor->rows[at + 1], sizeof(struct editor_row) * (editor->row_count - at - 1));
+
+ for (int j = at; j < editor->row_count; j++)
+ editor->rows[j].index--;
+
+ editor->row_count--;
+ editor->dirty = 1;
+}
+
+void row_insert_char(struct editor_state* editor, struct editor_row* row, int at, int c)
+{
+ if (at < 0 || at > row->size)
+ at = row->size;
+
+ row->chars = realloc(row->chars, row->size + 2);
+ memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
+ row->size++;
+ row->chars[at] = c;
+
+ update_row(editor, row);
+
+ editor->dirty = 1;
+}
+
+void row_append_string(struct editor_state* editor, struct editor_row* row, char* string, size_t length)
+{
+ row->chars = realloc(row->chars, row->size + length + 1);
+ memcpy(&row->chars[row->size], string, length);
+ row->size += length;
+ row->chars[row->size] = '\0';
+
+ update_row(editor, row);
+ editor->dirty = 1;
+}
+
+void row_delete_char(struct editor_state* editor, struct editor_row* row, int at)
+{
+ if (at < 0 || at >= row->size)
+ return;
+
+ memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
+ row->size--;
+ update_row(editor, row);
+ editor->dirty = 1;
+}
diff --git a/row.h b/row.h
new file mode 100644
index 0000000..f3c9708
--- /dev/null
+++ b/row.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <ctype.h>
+
+#include "editor.h"
+
+#define TAB_WIDTH 4
+
+struct editor_row {
+ int index;
+ int size;
+ char* chars;
+ int render_size;
+ char* render;
+ unsigned char* highlight;
+ int highlight_open_comment;
+};
+
+int row_x_to_display_x(struct editor_row* row, int x);
+int row_display_x_to_x(struct editor_row* row, int display_x);
+void update_row(struct editor_state* editor, struct editor_row* row);
+void insert_row(struct editor_state* editor, int at, char* string, size_t length);
+void free_row(struct editor_row* row);
+void delete_row(struct editor_state* editor, int at);
+void row_insert_char(struct editor_state* editor, struct editor_row* row, int at, int c);
+void row_append_string(struct editor_state* editor, struct editor_row* row, char* string, size_t length);
+void row_delete_char(struct editor_state* editor, struct editor_row* row, int at);
diff --git a/syntax.c b/syntax.c
new file mode 100644
index 0000000..ffaae4a
--- /dev/null
+++ b/syntax.c
@@ -0,0 +1,197 @@
+#include "syntax.h"
+
+#include <string.h>
+#include "editor.h"
+
+char* c_highlight_extensions[] = { ".c", ".h", ".cpp", ".cc", NULL };
+
+char* c_highlight_keywords[] = {
+ "switch", "if", "while", "for", "break", "continue", "return", "else",
+ "struct", "union", "typedef", "static", "enum", "class", "case",
+ "#include", "#define", "#ifdef", "#ifndef",
+
+ "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|",
+ "void|", NULL
+};
+
+struct editor_syntax highlight_database[] = {
+ {
+ "c",
+ c_highlight_extensions,
+ c_highlight_keywords,
+ "//", "/*", "*/",
+ HIGHLIGHT_FLAG_NUMBERS | HIGHLIGHT_FLAG_STRINGS
+ }
+};
+
+int is_separator(int c)
+{
+ return isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[];", c) != NULL;
+}
+
+void editor_update_syntax(struct editor_state* editor, struct editor_row* row)
+{
+ row->highlight = realloc(row->highlight, row->render_size);
+ memset(row->highlight, HIGHLIGHT_NORMAL, row->render_size);
+
+ if (editor->syntax == NULL)
+ return;
+
+ char** keywords = editor->syntax->keywords;
+
+ // TODO: Remove
+ char* single_line_comment_start = editor->syntax->single_line_comment_start;
+ char* multi_line_comment_start = editor->syntax->multi_line_comment_start;
+ char* multi_line_comment_end = editor->syntax->multi_line_comment_end;
+
+ int single_line_comment_start_length = single_line_comment_start ? strlen(single_line_comment_start) : 0;
+ int multi_line_comment_start_length = multi_line_comment_start ? strlen(multi_line_comment_start) : 0;
+ int multi_line_comment_end_length = multi_line_comment_end ? strlen(multi_line_comment_end) : 0;
+
+ int previous_separator = 1;
+ int in_string = 0;
+ int in_comment = (row->index > 0 && editor->rows[row->index - 1].highlight_open_comment);
+
+ int i = 0;
+ while (i < row->render_size) {
+ char c = row->render[i];
+ unsigned char previous_highlight = (i > 0) ? row->highlight[i - 1] : HIGHLIGHT_NORMAL;
+
+ if (single_line_comment_start_length && !in_string && !in_comment) {
+ if (!strncmp(&row->render[i], single_line_comment_start, single_line_comment_start_length)) {
+ memset(&row->highlight[i], HIGHLIGHT_COMMENT, row->render_size - i);
+ break;
+ }
+ }
+
+ if (multi_line_comment_start_length && multi_line_comment_end_length && !in_string) {
+ if (in_comment) {
+ row->highlight[i] = HIGHLIGHT_MULTILINE_COMMENT;
+ if (!strncmp(&row->render[i], multi_line_comment_end, multi_line_comment_end_length)) {
+ memset(&row->highlight[i], HIGHLIGHT_MULTILINE_COMMENT, multi_line_comment_end_length);
+
+ i += multi_line_comment_end_length;
+ in_comment = 0;
+ previous_separator = 1;
+ continue;
+ } else {
+ i++;
+ continue;
+ }
+ } else if (!strncmp(&row->render[i], multi_line_comment_start, multi_line_comment_start_length)) {
+ memset(&row->highlight[i], HIGHLIGHT_MULTILINE_COMMENT, multi_line_comment_start_length);
+ i += multi_line_comment_start_length;
+ in_comment = 1;
+ continue;
+ }
+ }
+
+ if (editor->syntax->flags & HIGHLIGHT_FLAG_STRINGS) {
+ if (in_string) {
+ row->highlight[i] = HIGHLIGHT_STRING;
+
+ if (c == '\\' && i + 1 < row->render_size) {
+ row->highlight[i + 1] = HIGHLIGHT_STRING;
+ i += 2;
+ continue;
+ }
+
+ if (c == in_string)
+ in_string = 0;
+
+ i++;
+ previous_separator = 1;
+ continue;
+ } else {
+ if (c == '"' || c == '\'') {
+ in_string = c;
+ row->highlight[i] = HIGHLIGHT_STRING;
+ i++;
+ continue;
+ }
+ }
+ }
+
+ if (editor->syntax->flags & HIGHLIGHT_FLAG_NUMBERS) {
+ if ((isdigit(c) && (previous_separator || previous_highlight == HIGHLIGHT_NUMBER)) || (c == '.' && previous_highlight == HIGHLIGHT_NUMBER)) {
+ row->highlight[i] = HIGHLIGHT_NUMBER;
+ i++;
+ previous_separator = 0;
+ continue;
+ }
+ }
+
+ if (previous_separator) {
+ int j;
+ for (j = 0; keywords[j]; j++) {
+ int keyword_length = strlen(keywords[j]);
+ int is_secondary = keywords[j][keyword_length - 1] == '|';
+
+ if (is_secondary)
+ keyword_length--;
+
+ if (!strncmp(&row->render[i], keywords[j], keyword_length) && is_separator(row->render[i + keyword_length])) {
+ memset(&row->highlight[i], is_secondary ? HIGHLIGHT_KEYWORD2 : HIGHLIGHT_KEYWORD1, keyword_length);
+ i += keyword_length;
+ break;
+ }
+ }
+ if (keywords[j] != NULL) {
+ previous_separator = 0;
+ continue;
+ }
+ }
+
+ previous_separator = is_separator(c);
+ i++;
+ }
+
+ int changed = (row->highlight_open_comment != in_comment);
+ row->highlight_open_comment = in_comment;
+ if (changed && row->index + 1 < editor->row_count)
+ editor_update_syntax(editor, &editor->rows[row->index + 1]);
+}
+
+int editor_syntax_to_colour(int highlight)
+{
+ switch (highlight) {
+ case HIGHLIGHT_MULTILINE_COMMENT:
+ case HIGHLIGHT_COMMENT: return 36;
+ case HIGHLIGHT_KEYWORD1: return 33;
+ case HIGHLIGHT_KEYWORD2: return 32;
+ case HIGHLIGHT_STRING: return 35;
+ case HIGHLIGHT_NUMBER: return 31;
+ case HIGHLIGHT_MATCH: return 34;
+ default: return 37;
+ }
+}
+
+void editor_select_syntax_highlight(struct editor_state* editor)
+{
+ editor->syntax = NULL;
+
+ if (editor->filename == NULL)
+ return;
+
+ char* extension = strrchr(editor->filename, '.');
+
+ for (unsigned int j = 0; j < HIGHLIGHT_DATABASE_ENTRY_COUNT; j++) {
+ struct editor_syntax* syntax = &highlight_database[j];
+ unsigned int i = 0;
+
+ while (syntax->filetype_match[i]) {
+ int is_extension = (syntax->filetype_match[i][0] == '.');
+ if ((is_extension && extension && !strcmp(extension, syntax->filetype_match[i])) || (!is_extension && strstr(editor->filename, syntax->filetype_match[i]))) {
+ editor->syntax = syntax;
+
+ int file_row;
+ for (file_row = 0; file_row < editor->row_count; file_row++) {
+ editor_update_syntax(editor, &editor->rows[file_row]);
+ }
+
+ return;
+ }
+ i++;
+ }
+ }
+}
diff --git a/syntax.h b/syntax.h
new file mode 100644
index 0000000..0cb673e
--- /dev/null
+++ b/syntax.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <stdlib.h>
+
+#include "editor.h"
+#include "row.h"
+
+#define HIGHLIGHT_FLAG_NUMBERS (1 << 0)
+#define HIGHLIGHT_FLAG_STRINGS (1 << 1)
+
+struct editor_syntax {
+ char* filetype;
+ char** filetype_match;
+ char** keywords;
+ char* single_line_comment_start;
+ char* multi_line_comment_start;
+ char* multi_line_comment_end;
+ int flags;
+};
+
+enum editor_highlight {
+ HIGHLIGHT_NORMAL = 0,
+ HIGHLIGHT_COMMENT,
+ HIGHLIGHT_MULTILINE_COMMENT,
+ HIGHLIGHT_KEYWORD1,
+ HIGHLIGHT_KEYWORD2,
+ HIGHLIGHT_STRING,
+ HIGHLIGHT_NUMBER,
+ HIGHLIGHT_MATCH
+};
+
+#define HIGHLIGHT_DATABASE_ENTRY_COUNT (sizeof(highlight_database) / sizeof(highlight_database[0]))
+
+void editor_update_syntax(struct editor_state* editor, struct editor_row* row);
+int editor_syntax_to_colour(int highlight);
+void editor_select_syntax_highlight(struct editor_state* editor);
diff --git a/terminal.c b/terminal.c
new file mode 100644
index 0000000..c24f46d
--- /dev/null
+++ b/terminal.c
@@ -0,0 +1,84 @@
+#include "terminal.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+static struct termios original_termios;
+
+void die(const char* message)
+{
+ /* Clear the screen */
+ write(STDOUT_FILENO, "\x1b[2J", 4);
+ write(STDOUT_FILENO, "\x1b[H", 3);
+
+ perror(message);
+ exit(1);
+}
+
+void disable_raw_mode()
+{
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios) == -1)
+ die("tcsetattr");
+}
+
+/* Disables any input and output processing from the terminal */
+void enable_raw_mode()
+{
+ if (tcgetattr(STDIN_FILENO, &original_termios) == -1)
+ die("tcgetattr");
+
+ atexit(disable_raw_mode);
+
+ struct termios raw = original_termios;
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ raw.c_oflag &= ~(OPOST);
+ raw.c_cflag |= (CS8);
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+
+ raw.c_cc[VMIN] = 0;
+ raw.c_cc[VTIME] = 1;
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
+ die("tcsetattr");
+}
+
+int get_cursor_position(int* rows, int* cols)
+{
+ char buffer[32];
+ unsigned int i = 0;
+
+ if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4)
+ return -1;
+
+ while (i < sizeof(buffer) - 1) {
+ if (read(STDIN_FILENO, &buffer[i], 1) != 1) break;
+ if (buffer[i] == 'R') break;
+ i++;
+ }
+ buffer[i] = '\0';
+
+ printf("\r\n&buffer[1]: '%s'\r\n", &buffer[1]);
+
+ if (buffer[0] != '\x1b' || buffer[1] != '[') return -1;
+ if (sscanf(&buffer[2], "%d;%d", rows, cols) != 2) return -1;
+
+ return 0;
+}
+
+int get_window_size(int* rows, int* cols)
+{
+ struct winsize size;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == -1 || size.ws_col == 0) {
+ if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12)
+ return -1;
+ return get_cursor_position(rows, cols);
+ } else {
+ *cols = size.ws_col;
+ *rows = size.ws_row;
+ return 0;
+ }
+}
diff --git a/terminal.h b/terminal.h
new file mode 100644
index 0000000..529ba74
--- /dev/null
+++ b/terminal.h
@@ -0,0 +1,7 @@
+#pragma once
+
+void die(const char* message);
+void disable_raw_mode();
+void enable_raw_mode();
+int get_cursor_position(int* rows, int* cols);
+int get_window_size(int* rows, int* cols);