diff options
author | cflip <cflip@cflip.net> | 2023-01-08 14:15:01 -0700 |
---|---|---|
committer | cflip <cflip@cflip.net> | 2023-01-08 14:15:01 -0700 |
commit | 91195d34e1f71246cadc41705004d66ab647087e (patch) | |
tree | 0d2f7e929b7b51590d270e2f22fb5a3621204bf2 |
Initial import of existing source code
This is based off of snaptoken's "Build Your Own Text Editor" tutorial
at https://viewsourcecode.org/snaptoken/kilo/.
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | buffer.c | 21 | ||||
-rw-r--r-- | buffer.h | 11 | ||||
-rw-r--r-- | editor.c | 361 | ||||
-rw-r--r-- | editor.h | 35 | ||||
-rw-r--r-- | file.c | 94 | ||||
-rw-r--r-- | file.h | 6 | ||||
-rw-r--r-- | input.c | 169 | ||||
-rw-r--r-- | input.h | 22 | ||||
-rw-r--r-- | main.c | 25 | ||||
-rw-r--r-- | row.c | 149 | ||||
-rw-r--r-- | row.h | 27 | ||||
-rw-r--r-- | syntax.c | 197 | ||||
-rw-r--r-- | syntax.h | 36 | ||||
-rw-r--r-- | terminal.c | 84 | ||||
-rw-r--r-- | terminal.h | 7 |
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} @@ -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); @@ -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)); +} @@ -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); @@ -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; +} @@ -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); @@ -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; +} @@ -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; +} @@ -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); |