tMerge source into single file - ve - a minimal text editor (work in progress) | |
git clone git://src.adamsgaard.dk/ve | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 7a8cfc0fc10085e622932f958235613b9ca8e149 | |
parent 0c629a2638b242c02e9dfce571b6738d64727b34 | |
Author: Anders Damsgaard <[email protected]> | |
Date: Wed, 7 Aug 2019 21:34:42 +0200 | |
Merge source into single file | |
Diffstat: | |
M Makefile | 14 +++++++------- | |
D src/edit.c | 60 -----------------------------… | |
D src/edit.h | 9 --------- | |
D src/find.c | 113 -----------------------------… | |
D src/find.h | 9 --------- | |
D src/input.c | 254 -----------------------------… | |
D src/input.h | 7 ------- | |
D src/io.c | 103 -----------------------------… | |
D src/io.h | 7 ------- | |
D src/main.c | 27 --------------------------- | |
D src/output.c | 215 -----------------------------… | |
D src/output.h | 14 -------------- | |
D src/row.c | 143 -----------------------------… | |
D src/row.h | 16 ---------------- | |
D src/terminal.c | 107 -----------------------------… | |
D src/terminal.h | 11 ----------- | |
D src/ve.c | 39 -----------------------------… | |
D src/ve.h | 39 -----------------------------… | |
A ve.c | 1062 +++++++++++++++++++++++++++++… | |
19 files changed, 1069 insertions(+), 1180 deletions(-) | |
--- | |
diff --git a/Makefile b/Makefile | |
t@@ -1,9 +1,9 @@ | |
+.POSIX: | |
+ | |
CFLAGS = -g -std=c99 -pedantic -Wall -Wextra | |
-#LDFLAGS = -lm | |
-SRCDIR = src | |
-SRC = $(wildcard $(SRCDIR)/*.c) | |
-OBJ = $(patsubst %.c,%.o,$(SRC)) | |
-HDR = $(wildcard *.h) | |
+LDFLAGS = | |
+SRC = ve.c | |
+OBJ = $(SRC:.c=.o) | |
BIN = ve | |
PREFIX ?= /usr/local | |
t@@ -12,7 +12,7 @@ STRIP ?= strip | |
default: $(BIN) | |
-$(BIN): $(OBJ) $(HDR) | |
+$(BIN): $(OBJ) | |
$(CC) $(LDFLAGS) $(OBJ) -o $@ | |
install: $(BIN) | |
t@@ -31,7 +31,7 @@ memtest: $(BIN) | |
valgrind --error-exitcode=1 --leak-check=full $(BIN) -v | |
clean: | |
- $(RM) $(SRCDIR)/*.o | |
+ $(RM) *.o | |
$(RM) $(BIN) | |
.PHONY: default install uninstall test memtest clean | |
diff --git a/src/edit.c b/src/edit.c | |
t@@ -1,60 +0,0 @@ | |
-#include "row.h" | |
-#include "ve.h" | |
- | |
-void | |
-editor_insert_char(int c) | |
-{ | |
- if (E.cursor_y == E.num_rows) | |
- editor_row_insert(E.num_rows, "", 0); | |
- editor_row_insert_char(&E.row[E.cursor_y], E.cursor_x, c); | |
- E.cursor_x++; | |
-} | |
- | |
-void | |
-editor_insert_new_line() | |
-{ | |
- eRow *row; | |
- if (E.cursor_x == 0) { | |
- editor_row_insert(E.cursor_y, "", 0); | |
- } else { | |
- row = &E.row[E.cursor_y]; | |
- editor_row_insert(E.cursor_y + 1, &row->chars[E.cursor_x], | |
- row->size - E.cursor_x); | |
- row = &E.row[E.cursor_y]; | |
- row->size = E.cursor_x; | |
- row->chars[row->size] = '\0'; | |
- editor_row_update(row); | |
- } | |
- E.cursor_y++; | |
- E.cursor_x = 0; | |
-} | |
- | |
-/* delete a character before the cursor, and allow for deletion across rows */ | |
-void | |
-editor_delete_char_left() | |
-{ | |
- eRow *row; | |
- if (E.cursor_y == E.num_rows || (E.cursor_x == 0 && E.cursor_y == 0)) | |
- return; | |
- | |
- row = &E.row[E.cursor_y]; | |
- if (E.cursor_x > 0) { | |
- editor_row_delete_char(row, E.cursor_x - 1); | |
- E.cursor_x--; | |
- } else { | |
- E.cursor_x = E.row[E.cursor_y - 1].size; | |
- editor_row_append_string(&E.row[E.cursor_y - 1], | |
- row->chars, row->size); | |
- editor_row_delete(E.cursor_y); | |
- E.cursor_y--; | |
- } | |
-} | |
- | |
-void | |
-editor_delete_char_right() | |
-{ | |
- if (E.cursor_y == E.num_rows) | |
- return; | |
- eRow *row = &E.row[E.cursor_y]; | |
- editor_row_delete_char(row, E.cursor_x); | |
-} | |
diff --git a/src/edit.h b/src/edit.h | |
t@@ -1,9 +0,0 @@ | |
-#ifndef EDIT_H_ | |
-#define EDIT_H_ | |
- | |
-void editor_insert_char(int c); | |
-void editor_insert_new_line(); | |
-void editor_delete_char_left(); | |
-void editor_delete_char_right(); | |
- | |
-#endif | |
diff --git a/src/find.c b/src/find.c | |
t@@ -1,113 +0,0 @@ | |
-/* add feature test macro for strdup compatibility */ | |
-#define _DEFAULT_SOURCE | |
-#define _BSD_SOURCE | |
-#define _GNU_SOURCE | |
- | |
-#include <stdlib.h> | |
-#include <string.h> | |
-#include "input.h" | |
-#include "ve.h" | |
-#include "row.h" | |
-#include "output.h" | |
- | |
-/* reverse of strstr (3) */ | |
-char* | |
-strrstr(char *haystack, char *needle, | |
- size_t haystack_length, size_t needle_length) | |
-{ | |
- char *cp; | |
- for (cp = haystack + haystack_length - needle_length; | |
- cp >= haystack; | |
- cp--) { | |
- if (strncmp(cp, needle, needle_length) == 0) | |
- return cp; | |
- } | |
- return NULL; | |
-} | |
- | |
-/* find E.find_query from current cursor position moving in E.direction | |
- * if opposite_direction = 0, and opposite E.direction if | |
- * opposite_direction = 1 */ | |
-void | |
-editor_find_next_occurence(int opposite_direction) | |
-{ | |
- int y, y_inc, x_offset, query_len; | |
- eRow *row; | |
- char *match; | |
- | |
- if (!E.find_query) | |
- return; | |
- | |
- if ((E.find_direction && !opposite_direction) || | |
- (!E.find_direction && opposite_direction)) | |
- y_inc = +1; | |
- else | |
- y_inc = -1; | |
- | |
- x_offset = 0; | |
- query_len = strlen(E.find_query); | |
- /* from cursor until end of document */ | |
- for (y = E.cursor_y; | |
- y != ((y_inc == +1) ? E.num_rows : -1); | |
- y += y_inc) { | |
- row = &E.row[y]; | |
- | |
- if (y == E.cursor_y) | |
- x_offset = y_inc + E.cursor_x; | |
- else | |
- x_offset = 0; | |
- | |
- if (y_inc == +1) { | |
- match = strstr(row->chars + x_offset, E.find_query); | |
- if (match) | |
- break; | |
- } else { | |
- match = strrstr(row->chars, E.find_query, | |
- (y == E.cursor_y) ? E.cursor_x : row->… | |
- query_len); | |
- if (match) | |
- break; | |
- } | |
- } | |
- | |
- if (match) { | |
- E.cursor_y = y; | |
- E.cursor_x = match - row->chars; | |
- /* E.row_offset = E.num_rows; */ /* put line to top of screen … | |
- | |
- } else { | |
- /* from other end of file until cursor */ | |
- for (y = (y_inc == +1) ? 0 : E.num_rows - 1; | |
- y != E.cursor_y + y_inc; | |
- y += y_inc) { | |
- row = &E.row[y]; | |
- match = strstr(row->chars, E.find_query); | |
- if (match) | |
- break; | |
- } | |
- if (match) { | |
- E.cursor_y = y; | |
- E.cursor_x = editor_row_cursor_rx_to_x(row, match - ro… | |
- } | |
- } | |
-} | |
- | |
-/* direction = 1 is forward, 0 is backward */ | |
-void | |
-editor_find(int direction) | |
-{ | |
- char *query; | |
- | |
- if (direction) | |
- query = editor_prompt("/%s"); | |
- else | |
- query = editor_prompt("?%s"); | |
- if (query == NULL) | |
- return; | |
- | |
- E.find_direction = direction; | |
- free(E.find_query); | |
- E.find_query = strdup(query); | |
- editor_find_next_occurence(0); | |
- free(query); | |
-} | |
diff --git a/src/find.h b/src/find.h | |
t@@ -1,9 +0,0 @@ | |
-#ifndef FIND_H_ | |
-#define FIND_H_ | |
- | |
-char* strrstr(char *haystack, char *needle, | |
- size_t haystack_length, size_t needle_length); | |
-void editor_find_next_occurence(int opposite_direction); | |
-void editor_find(int direction); | |
- | |
-#endif | |
diff --git a/src/input.c b/src/input.c | |
t@@ -1,254 +0,0 @@ | |
-#include <unistd.h> | |
-#include <stdlib.h> | |
-#include <ctype.h> | |
-#include <string.h> | |
-#include "ve.h" | |
-#include "terminal.h" | |
-#include "edit.h" | |
-#include "output.h" | |
-#include "io.h" | |
-#include "row.h" | |
-#include "find.h" | |
- | |
-#define CTRL_KEY(k) ((k) & 0x1f) | |
- | |
-/* prompt is expected to be a format string containing a %s */ | |
-char* | |
-editor_prompt(char *prompt) | |
-{ | |
- size_t bufsize, buflen; | |
- char *buf; | |
- int c; | |
- | |
- bufsize = 128; | |
- buflen = 0; | |
- buf = malloc(bufsize); | |
- buf[0] = '\0'; | |
- | |
- while (1) { | |
- editor_set_status_message(prompt, buf); | |
- editor_refresh_screen(); | |
- editor_place_cursor(strlen(prompt) - 1 + strlen(buf), | |
- E.screen_rows + 2); | |
- | |
- c = editor_read_key(); | |
- if (c == CTRL_KEY('h') || c == 127) { /* detect backspace */ | |
- if (buflen != 0) | |
- buf[--buflen] = '\0'; | |
- } else if (c == '\x1b' || (c == '\r' && !buflen)) { /* detect … | |
- editor_set_status_message(""); | |
- free(buf); | |
- return NULL; | |
- } else if (c == '\r') { | |
- if (buflen != 0) { | |
- editor_set_status_message(""); | |
- return buf; | |
- } | |
- } else if (!iscntrl(c) && c < 128) { | |
- if (buflen >= bufsize - 1) { | |
- bufsize *= 2; | |
- buf = realloc(buf, bufsize); | |
- } | |
- buf[buflen++] = c; | |
- buf[buflen] = '\0'; | |
- } | |
- } | |
-} | |
- | |
-/* move cursor according to screen, file, and line limits */ | |
-void | |
-editor_move_cursor(char key) | |
-{ | |
- int row_len; | |
- eRow *row; | |
- | |
- switch(key) { | |
- case 'h': | |
- if (E.cursor_x != 0) { | |
- E.cursor_x--; | |
- } else if (E.cursor_y > 0) { | |
- E.cursor_y--; | |
- E.cursor_x = E.row[E.cursor_y].size; | |
- } | |
- break; | |
- case 'j': | |
- if (E.cursor_y < E.num_rows - 1) | |
- E.cursor_y++; | |
- break; | |
- case 'k': | |
- if (E.cursor_y != 0) | |
- E.cursor_y--; | |
- break; | |
- case 'l': | |
- row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cur… | |
- if (row && E.cursor_x < row->size - 1) { | |
- E.cursor_x++; | |
- } else if (row && E.cursor_x == row->size && | |
- E.cursor_y < E.num_rows - 1) { | |
- E.cursor_y++; | |
- E.cursor_x = 0; | |
- } | |
- break; | |
- } | |
- | |
- /* do not allow navigation past EOL by vertical navigation */ | |
- row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cursor_y]; | |
- row_len = row ? row->size : 0; | |
- if (E.cursor_x > row_len) | |
- E.cursor_x = row_len; | |
-} | |
- | |
-void | |
-editor_process_keypress() | |
-{ | |
- char c; | |
- int i; | |
- | |
- c = editor_read_key(); | |
- | |
- if (E.mode == 0) { /* normal mode */ | |
- switch (c) { | |
- case 'i': | |
- E.mode = 1; | |
- break; | |
- case 'a': | |
- editor_move_cursor('l'); | |
- E.mode = 1; | |
- break; | |
- | |
- case CTRL_KEY('q'): | |
- if (E.file_changed) { | |
- editor_set_status_message("error: " | |
- "file ha… | |
- "Press C… | |
- break; | |
- } else { | |
- write(STDOUT_FILENO, "\x1b[2J", 4); /*… | |
- write(STDOUT_FILENO, "\x1b[H", 3); | |
- exit(0); | |
- break; | |
- } | |
- case CTRL_KEY('x'): | |
- write(STDOUT_FILENO, "\x1b[2J", 4); /* clear s… | |
- write(STDOUT_FILENO, "\x1b[H", 3); | |
- exit(0); | |
- break; | |
- case 'h': | |
- case 'j': | |
- case 'k': | |
- case 'l': | |
- editor_move_cursor(c); | |
- break; | |
- | |
- case CTRL_KEY('w'): | |
- file_save(E.filename); | |
- break; | |
- | |
- case CTRL_KEY('f'): | |
- i = E.screen_rows; | |
- while (i--) | |
- editor_move_cursor('j'); | |
- break; | |
- case CTRL_KEY('b'): | |
- i = E.screen_rows; | |
- while (i--) | |
- editor_move_cursor('k'); | |
- break; | |
- | |
- case CTRL_KEY('d'): | |
- i = E.screen_rows/2; | |
- while (i--) | |
- editor_move_cursor('j'); | |
- break; | |
- case CTRL_KEY('u'): | |
- i = E.screen_rows/2; | |
- while (i--) | |
- editor_move_cursor('k'); | |
- break; | |
- | |
- case '0': | |
- E.cursor_x = 0; | |
- break; | |
- case '$': | |
- if (E.cursor_y < E.num_rows) | |
- E.cursor_x = E.row[E.cursor_y].size - … | |
- break; | |
- | |
- case 'g': | |
- E.cursor_x = 0; | |
- E.cursor_y = 0; | |
- break; | |
- case 'G': | |
- E.cursor_x = 0; | |
- E.cursor_y = E.num_rows - 1; | |
- break; | |
- | |
- case 'x': | |
- editor_delete_char_right(); | |
- break; | |
- case 'd': | |
- editor_row_delete(E.cursor_y); | |
- editor_move_cursor('h'); | |
- break; | |
- | |
- case 'o': | |
- if (E.cursor_y < E.num_rows) | |
- E.cursor_x = E.row[E.cursor_y].size; | |
- editor_insert_new_line(); | |
- E.mode = 1; | |
- break; | |
- case 'O': | |
- E.cursor_x = 0; | |
- editor_insert_new_line(); | |
- editor_move_cursor('k'); | |
- E.mode = 1; | |
- break; | |
- | |
- case 'I': | |
- E.cursor_x = 0; | |
- E.mode = 1; | |
- break; | |
- case 'A': | |
- if (E.cursor_y < E.num_rows) | |
- E.cursor_x = E.row[E.cursor_y].size; | |
- E.mode = 1; | |
- break; | |
- | |
- case '/': | |
- editor_find(1); | |
- break; | |
- case '?': | |
- editor_find(0); | |
- break; | |
- case 'n': | |
- editor_find_next_occurence(0); | |
- break; | |
- case 'N': | |
- editor_find_next_occurence(1); | |
- break; | |
- } | |
- } else if (E.mode == 1) { /* insert mode */ | |
- switch (c) { | |
- case CTRL_KEY('c'): | |
- case '\x1b': /* escape */ | |
- E.mode = 0; | |
- break; | |
- | |
- case CTRL_KEY('\r'): /* enter */ | |
- editor_insert_new_line(); | |
- break; | |
- | |
- case 127: /* backspace */ | |
- case CTRL_KEY('h'): | |
- editor_delete_char_left(); | |
- break; | |
- | |
- case CTRL_KEY('l'): | |
- break; | |
- | |
- default: | |
- editor_insert_char(c); | |
- break; | |
- } | |
- } | |
-} | |
diff --git a/src/input.h b/src/input.h | |
t@@ -1,7 +0,0 @@ | |
-#ifndef INPUT_H_ | |
-#define INPUT_H_ | |
- | |
-char* editor_prompt(char *prompt); | |
-void editor_process_keypress(); | |
- | |
-#endif | |
diff --git a/src/io.c b/src/io.c | |
t@@ -1,103 +0,0 @@ | |
-/* add feature test macro for getline compatibility */ | |
-#define _DEFAULT_SOURCE | |
-#define _BSD_SOURCE | |
-#define _GNU_SOURCE | |
- | |
-#include <stdlib.h> | |
-#include <stdio.h> | |
-#include <sys/types.h> | |
-#include <string.h> | |
-#include <fcntl.h> | |
-#include <unistd.h> | |
-#include <errno.h> | |
-#include "ve.h" | |
-#include "terminal.h" | |
-#include "row.h" | |
-#include "output.h" | |
-#include "input.h" | |
- | |
-void | |
-file_open(char *filename) | |
-{ | |
- free(E.filename); | |
- E.filename = strdup(filename); | |
- | |
- FILE *fp; | |
- char *line; | |
- size_t linecap; | |
- ssize_t linelen; | |
- | |
- fp = fopen(filename, "r"); | |
- if (!fp) | |
- die("fopen in file_open"); | |
- | |
- line = NULL; | |
- linecap = 0; | |
- while ((linelen = getline(&line, &linecap, fp)) != -1) { | |
- while (linelen > 0 && (line[linelen - 1] == '\n' || | |
- line[linelen - 1] == '\r')) | |
- linelen--; | |
- editor_row_insert(E.num_rows, line, linelen); | |
- } | |
- free(line); | |
- fclose(fp); | |
- E.file_changed = 0; | |
-} | |
- | |
-/* convert rows to one long char array of length buflen */ | |
-char* | |
-editor_concatenate_rows(int *buflen) | |
-{ | |
- int totlen, j; | |
- char *buf, *p; | |
- | |
- totlen = 0; | |
- for (j=0; j<E.num_rows; ++j) | |
- totlen += E.row[j].size + 1; /* add space for newline char */ | |
- *buflen = totlen; | |
- | |
- buf = malloc(totlen); | |
- p = buf; | |
- for (j=0; j<E.num_rows; ++j) { | |
- memcpy(p, E.row[j].chars, E.row[j].size); | |
- p += E.row[j].size; | |
- *p = '\n'; | |
- p++; | |
- } | |
- return buf; | |
-} | |
- | |
-void | |
-file_save(char *filename) | |
-{ | |
- int len, fd; | |
- char *buf; | |
- | |
- if (filename == NULL) { | |
- filename = editor_prompt("save as: %s"); | |
- if (filename == NULL) { | |
- editor_set_status_message("save aborted"); | |
- return; | |
- } | |
- } | |
- | |
- buf = editor_concatenate_rows(&len); | |
- | |
- fd = open(filename, O_RDWR | O_CREAT, 0644); | |
- if (fd != -1) { | |
- if (ftruncate(fd, len) != -1) { | |
- if (write(fd, buf, len) == len) { | |
- close(fd); | |
- free(buf); | |
- E.filename = filename; | |
- E.file_changed = 0; | |
- editor_set_status_message("%d bytes written to… | |
- return; | |
- } | |
- } | |
- close(fd); | |
- } | |
- free(buf); | |
- editor_set_status_message("error: can't save! I/O error %s", | |
- strerror(errno)); | |
-} | |
diff --git a/src/io.h b/src/io.h | |
t@@ -1,7 +0,0 @@ | |
-#ifndef IO_H_ | |
-#define IO_H_ | |
- | |
-void file_open(char *filename); | |
-void file_save(char *filename); | |
- | |
-#endif | |
diff --git a/src/main.c b/src/main.c | |
t@@ -1,27 +0,0 @@ | |
-#include <stdlib.h> | |
-#include "terminal.h" | |
-#include "output.h" | |
-#include "input.h" | |
-#include "io.h" | |
-#include "output.h" | |
-#include "ve.h" | |
- | |
-int | |
-main(int argc, char* argv[]) | |
-{ | |
- enable_raw_mode(); | |
- init_editor(); | |
- | |
- /* TODO: proper argument handling */ | |
- if (argc >= 2) { | |
- file_open(argv[1]); | |
- } | |
- | |
- editor_set_status_message("%s v%s", PROGNAME, VERSION); | |
- | |
- while (1) { | |
- editor_refresh_screen(); | |
- editor_process_keypress(); | |
- } | |
- return 0; | |
-} | |
diff --git a/src/output.c b/src/output.c | |
t@@ -1,215 +0,0 @@ | |
-#include <unistd.h> | |
-#include <string.h> | |
-#include <stdlib.h> | |
-#include <stdio.h> | |
-#include <stdarg.h> | |
-#include <time.h> | |
-#include "terminal.h" | |
-#include "output.h" | |
-#include "row.h" | |
-#include "ve.h" | |
- | |
-/* reallocate append buffer to hold string s */ | |
-void | |
-ab_append(struct abuf *ab, const char *s, int len) | |
-{ | |
- char *new; | |
- new = realloc(ab->b, ab->len + len); | |
- | |
- if (new == NULL) | |
- return; | |
- memcpy(&new[ab->len], s, len); | |
- ab->b = new; | |
- ab->len += len; | |
-} | |
- | |
-void | |
-ab_free(struct abuf *ab) { | |
- free(ab->b); | |
-} | |
- | |
-int | |
-show_status_message() | |
-{ | |
- if (E.show_status && | |
- strlen(E.status_msg) && | |
- time(NULL) - E.status_msg_time < STATUS_MESSAGE_TIMEOUT) { | |
- return 1; | |
- } else { | |
- if (E.show_status) { | |
- E.show_status = 0; | |
- E.screen_rows++; | |
- } | |
- return 0; | |
- } | |
-} | |
- | |
-/* draw status line consisting of left and right components. | |
- * if the screen is narrower than both parts, just show the left status, | |
- * truncated, if necessary. */ | |
-void | |
-editor_draw_status(struct abuf *ab) | |
-{ | |
- char left_status[512], right_status[512]; | |
- int left_status_len, right_status_len, padding; | |
- int percentage; | |
- | |
- left_status_len = 0; | |
- switch (E.mode) { | |
- case 1: | |
- left_status_len = snprintf(left_status, sizeof(left_st… | |
- "INSERT "); | |
- break; | |
- case 2: | |
- left_status_len = snprintf(left_status, sizeof(left_st… | |
- "VISUAL "); | |
- break; | |
- } | |
- left_status_len += snprintf(left_status + left_status_len, | |
- sizeof(left_status), | |
- "%.20s %s", | |
- E.filename ? E.filename : "[unnamed]", | |
- E.file_changed ? "[+] " : ""); | |
- | |
- percentage = (int)((float)(E.cursor_y)/(E.num_rows-1)*100); | |
- if (percentage < 0) percentage = 0; | |
- right_status_len = snprintf(right_status, sizeof(right_status), | |
- "%d%% < %d, %d", | |
- percentage, | |
- E.cursor_x+1, E.cursor_y+1); | |
- | |
- if (left_status_len > E.screen_columns) | |
- left_status_len = E.screen_columns; | |
- | |
- padding = E.screen_columns - left_status_len - right_status_len; | |
- if (padding < 0) { | |
- if (left_status_len < E.screen_columns) | |
- ab_append(ab, left_status, left_status_len); | |
- else | |
- ab_append(ab, left_status, E.screen_columns); | |
- } else { | |
- ab_append(ab, left_status, left_status_len); | |
- while (padding--) | |
- ab_append(ab, " ", 1); | |
- ab_append(ab, right_status, right_status_len); | |
- } | |
- | |
- if (show_status_message()) { | |
- ab_append(ab, "\x1b[m", 3); | |
- ab_append(ab, "\r\n", 2); | |
- } | |
-} | |
- | |
-/* draw status message if as long as it is specified and it is not too old */ | |
-void | |
-editor_draw_status_message(struct abuf *ab) | |
-{ | |
- int msglen; | |
- ab_append(ab, "\x1b[K", 3); | |
- msglen = strlen(E.status_msg); | |
- if (msglen > E.screen_columns) | |
- msglen = E.screen_columns; | |
- if (show_status_message()) | |
- ab_append(ab, E.status_msg, msglen); | |
-} | |
- | |
-/* set vertical offset between file and screen when hitting the boundaries */ | |
-void | |
-editor_scroll() | |
-{ | |
- E.cursor_rx = 0; | |
- if (E.cursor_y < E.num_rows) | |
- E.cursor_rx = editor_row_cursor_x_to_rx(&E.row[E.cursor_y], | |
- E.cursor_x); | |
- | |
- if (E.cursor_y < E.row_offset) | |
- E.row_offset = E.cursor_y; | |
- else if (E.cursor_y >= E.row_offset + E.screen_rows) | |
- E.row_offset = E.cursor_y - E.screen_rows + 1; | |
- | |
- if (E.cursor_rx < E.column_offset) | |
- E.column_offset = E.cursor_rx; | |
- else if (E.cursor_rx >= E.column_offset + E.screen_columns) | |
- E.column_offset = E.cursor_rx - E.screen_columns + 1; | |
-} | |
- | |
-/* draw editor screen. | |
- * show tilde characters after EOF, and show status on last line */ | |
-void | |
-editor_draw_rows(struct abuf *ab) | |
-{ | |
- int y, len, file_row; | |
- for (y = 0; y < E.screen_rows; ++y) { | |
- file_row = y + E.row_offset; | |
- if (file_row < E.num_rows) { | |
- len = E.row[file_row].rsize - E.column_offset; | |
- if (len < 0) | |
- len = 0; | |
- if (len > E.screen_columns) | |
- len = E.screen_columns; | |
- ab_append(ab, &E.row[file_row].rchars[E.column_offset]… | |
- } else { | |
- ab_append(ab, "~", 1); | |
- } | |
- | |
- ab_append(ab, "\x1b[K", 3); /* erase to end of line */ | |
- ab_append(ab, "\r\n", 2); | |
- } | |
-} | |
- | |
-/* fill output append buffer and write to terminal. | |
- * move cursor to left before repaint, and to cursor position after. | |
- * hide the cursor when repainting. | |
- * VT100 escape sequences: | |
- * - http://vt100.net/docs/vt100-ug/chapter3.html | |
- * - http://vt100.net/docs/vt100-ug/chapter3.html#CUP */ | |
-void | |
-editor_refresh_screen() | |
-{ | |
- char buf[32]; | |
- struct abuf ab = ABUF_INIT; | |
- | |
- show_status_message(); | |
- editor_scroll(); | |
- | |
- ab_append(&ab, "\x1b[?25l", 6); /* hide cursor */ | |
- ab_append(&ab, "\x1b[H", 3); /* cursor to home */ | |
- | |
- editor_draw_rows(&ab); | |
- editor_draw_status(&ab); | |
- editor_draw_status_message(&ab); | |
- | |
- snprintf(buf, sizeof(buf), "\x1b[%d;%dH", | |
- (E.cursor_y - E.row_offset)+1, | |
- (E.cursor_rx - E.column_offset)+1); | |
- ab_append(&ab, buf, strlen(buf)); | |
- | |
- ab_append(&ab, "\x1b[?25h", 6); /* show cursor */ | |
- | |
- write(STDOUT_FILENO, ab.b, ab.len); | |
- ab_free(&ab); | |
-} | |
- | |
-void | |
-editor_place_cursor(int cx, int cy) | |
-{ | |
- char buf[128]; | |
- snprintf(buf, sizeof(buf), "\x1b[%d;%dH", cy, cx); | |
- write(STDOUT_FILENO, buf, strlen(buf)); | |
-} | |
- | |
-/* set status message text, uses same format as printf */ | |
-void | |
-editor_set_status_message(const char *fmt, ...) | |
-{ | |
- va_list ap; | |
- va_start(ap, fmt); | |
- vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap); | |
- va_end(ap); | |
- E.status_msg_time = time(NULL); | |
- | |
- if (!E.show_status) { | |
- E.screen_rows--; | |
- E.show_status = 1; | |
- } | |
-} | |
diff --git a/src/output.h b/src/output.h | |
t@@ -1,14 +0,0 @@ | |
-#ifndef OUTPUT_H_ | |
-#define OUTPUT_H_ | |
- | |
-struct abuf { | |
- char *b; | |
- int len; | |
-}; | |
-#define ABUF_INIT {NULL, 0} | |
- | |
-void editor_refresh_screen(); | |
-void editor_set_status_message(const char *fmt, ...); | |
-void editor_place_cursor(int cx, int cy); | |
- | |
-#endif | |
diff --git a/src/row.c b/src/row.c | |
t@@ -1,143 +0,0 @@ | |
-#include <stdlib.h> | |
-#include <string.h> | |
-#include "ve.h" | |
- | |
-/* navigate over tab-representative string as one character */ | |
-int | |
-editor_row_cursor_x_to_rx(eRow *row, int cursor_x) | |
-{ | |
- int rx, j; | |
- rx = 0; | |
- for (j=0; j<cursor_x; ++j) { | |
- if (row->chars[j] == '\t') | |
- rx += (TAB_WIDTH - 1) - (rx % TAB_WIDTH); | |
- rx++; | |
- } | |
- return rx; | |
-} | |
- | |
-/* translate on-screen position to data position */ | |
-int | |
-editor_row_cursor_rx_to_x(eRow *row, int cursor_rx) | |
-{ | |
- int cur_rx, cx; | |
- cur_rx = 0; | |
- for (cx=0; cx<row->size; ++cx) { | |
- if (row->chars[cx] == '\t') | |
- cur_rx += (TAB_WIDTH - 1) - (cur_rx % TAB_WIDTH); | |
- cur_rx++; | |
- | |
- if (cur_rx > cursor_rx) | |
- return cx; | |
- } | |
- return cx; | |
-} | |
- | |
-/* translate tabs before display */ | |
-void | |
-editor_row_update(eRow* row) | |
-{ | |
- int j, idx, tabs; | |
- | |
- free(row->rchars); | |
- row->rchars = malloc(row->size + 1); | |
- | |
- tabs = 0; | |
- for (j=0; j<row->size; ++j) | |
- if (row->chars[j] == '\t') | |
- tabs++; | |
- | |
- free(row->rchars); | |
- row->rchars = malloc(row->size + tabs*(TAB_WIDTH - 1) + 1); | |
- | |
- idx = 0; | |
- for (j=0; j<row->size; ++j) { | |
- if (row->chars[j] == '\t') { | |
- row->rchars[idx++] = '>'; | |
- while (idx % TAB_WIDTH != 0) | |
- row->rchars[idx++] = ' '; | |
- } else { | |
- row->rchars[idx++] = row->chars[j]; | |
- } | |
- } | |
- row->rchars[idx] = '\0'; | |
- row->rsize = idx; | |
-} | |
- | |
-/* add row to buffer */ | |
-void | |
-editor_row_insert(int i, char *s, size_t len) | |
-{ | |
- if (i<0 || i>E.num_rows) | |
- return; | |
- | |
- E.row = realloc(E.row, sizeof(eRow) * (E.num_rows + 1)); | |
- memmove(&E.row[i+1], &E.row[i], sizeof(eRow) * (E.num_rows - i)); | |
- | |
- E.row[i].size = len; | |
- E.row[i].chars = malloc(len + 1); | |
- memcpy(E.row[i].chars, s, len); | |
- E.row[i].chars[len] = '\0'; | |
- | |
- E.row[i].rsize = 0; | |
- E.row[i].rchars = NULL; | |
- editor_row_update(&E.row[i]); | |
- | |
- ++E.num_rows; | |
- E.file_changed = 1; | |
-} | |
- | |
-/* insert character to row, making sure to allocate memory accordingly */ | |
-void | |
-editor_row_insert_char(eRow *row, int i, int c) | |
-{ | |
- if (i<0 || i>row->size) | |
- i = row->size; | |
- row->chars = realloc(row->chars, row->size + 2); | |
- memmove(&row->chars[i+1], &row->chars[i], row->size - i+1); | |
- row->size++; | |
- row->chars[i] = c; | |
- editor_row_update(row); | |
- E.file_changed = 1; | |
-} | |
- | |
-/* append a string to the end of a row */ | |
-void | |
-editor_row_append_string(eRow *row, char *s, size_t len) | |
-{ | |
- row->chars = realloc(row->chars, row->size + len + 1); | |
- memcpy(&row->chars[row->size], s, len); | |
- row->size += len; | |
- row->chars[row->size] = '\0'; | |
- editor_row_update(row); | |
- E.file_changed = 1; | |
-} | |
- | |
-void | |
-editor_row_delete_char(eRow *row, int i) | |
-{ | |
- if (i<0 || i>=row->size) | |
- return; | |
- memmove(&row->chars[i], &row->chars[i+1], row->size - i); | |
- row->size--; | |
- editor_row_update(row); | |
- E.file_changed = 1; | |
-} | |
- | |
-void | |
-editor_row_free(eRow *row) | |
-{ | |
- free(row->rchars); | |
- free(row->chars); | |
-} | |
- | |
-void | |
-editor_row_delete(int i) | |
-{ | |
- if (i<0 || i>=E.num_rows) | |
- return; | |
- editor_row_free(&E.row[i]); | |
- memmove(&E.row[i], &E.row[i+1], sizeof(eRow)*(E.num_rows - i - 1)); | |
- E.num_rows--; | |
- E.file_changed = 1; | |
-} | |
diff --git a/src/row.h b/src/row.h | |
t@@ -1,16 +0,0 @@ | |
-#ifndef ROW_H_ | |
-#define ROW_H_ | |
- | |
-#include "ve.h" | |
- | |
-int editor_row_cursor_x_to_rx(eRow *row, int cursor_x); | |
-int editor_row_cursor_rx_to_x(eRow *row, int cursor_rx); | |
-void editor_row_update(eRow* row); | |
-void editor_row_append_string(eRow *row, char *s, size_t len); | |
-void editor_row_insert(int i, char *s, size_t len); | |
-void editor_row_insert_char(eRow *row, int i, int c); | |
-void editor_row_delete_char(eRow *row, int i); | |
-void editor_row_delete(int i); | |
-void editor_row_free(eRow *row); | |
- | |
-#endif | |
diff --git a/src/terminal.c b/src/terminal.c | |
t@@ -1,107 +0,0 @@ | |
-#include <ctype.h> | |
-#include <errno.h> | |
-#include <stdio.h> | |
-#include <stdlib.h> | |
-#include <termios.h> | |
-#include <unistd.h> | |
-#include <sys/ioctl.h> | |
-#include "ve.h" | |
- | |
-struct editor_config E; | |
- | |
-void | |
-die(const char *s) | |
-{ | |
- /* clear screen on exit */ | |
- write(STDOUT_FILENO, "\x1b[2J", 4); | |
- write(STDOUT_FILENO, "\x1b[H", 3); | |
- perror(s); | |
- exit(1); | |
-} | |
- | |
-void | |
-disable_raw_mode() | |
-{ | |
- if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) | |
- die("tcsetattr in disable_raw_mode()"); | |
-} | |
- | |
-void | |
-enable_raw_mode() | |
-{ | |
- struct termios raw; | |
- | |
- if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) | |
- die("tcgetattr in enable_raw_mode()"); | |
- atexit(disable_raw_mode); | |
- | |
- /* fix modifier keys, set 8 bits per char, ignore interrupts */ | |
- raw = E.orig_termios; | |
- raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); | |
- raw.c_oflag &= ~(OPOST); | |
- raw.c_cflag |= (CS8); | |
- raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | |
- | |
- /* set read() timeout in tenths of seconds */ | |
- raw.c_cc[VMIN] = 0; | |
- raw.c_cc[VTIME] = 1; | |
- | |
- /* apply terminal settings */ | |
- if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) | |
- die("tcsetattr in enable_raw_mode()"); | |
-} | |
- | |
-char | |
-editor_read_key() | |
-{ | |
- int nread; | |
- char c; | |
- while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { | |
- if (nread == -1 && errno != EAGAIN) | |
- die("read in editor_read_key()"); | |
- } | |
- return c; | |
-} | |
- | |
-/* get screen size by moving cursor and get current position */ | |
-int | |
-get_cursor_position(int *rows, int *cols) { | |
- char buf[32]; | |
- unsigned int i; | |
- | |
- if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) | |
- return -1; | |
- | |
- i = 0; | |
- while (i < sizeof(buf) - 1) { | |
- if (read(STDIN_FILENO, &buf[i], 1) != 1) | |
- break; | |
- if (buf[i] == 'R') | |
- break; | |
- ++i; | |
- } | |
- buf[i] = '\0'; | |
- | |
- if (buf[0] != '\x1b' || buf[1] != '[') | |
- return -1; | |
- if (scanf(&buf[2], "%d;%d", rows, cols) != 2) | |
- return -1; | |
- return 0; | |
-} | |
- | |
-int | |
-get_window_size(int *rows, int *cols) | |
-{ | |
- struct winsize ws; | |
- | |
- if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { | |
- /* fallback screen size detection */ | |
- if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) | |
- return -1; | |
- return get_cursor_position(rows, cols); | |
- } else { | |
- *cols = ws.ws_col; | |
- *rows = ws.ws_row; | |
- return 0; | |
- } | |
-} | |
diff --git a/src/terminal.h b/src/terminal.h | |
t@@ -1,11 +0,0 @@ | |
-#ifndef TERMINAL_H_ | |
-#define TERMINAL_H_ | |
- | |
-void die(const char *s); | |
-void disable_raw_mode(); | |
-void enable_raw_mode(); | |
-char editor_read_key(); | |
-int get_window_size(int *rows, int *cols); | |
-void init_editor(); | |
- | |
-#endif | |
diff --git a/src/ve.c b/src/ve.c | |
t@@ -1,39 +0,0 @@ | |
-#include <stdlib.h> | |
-#include "terminal.h" | |
-#include "row.h" | |
-#include "ve.h" | |
- | |
-struct editor_config E; | |
- | |
-void | |
-deinit_editor() { | |
- int i; | |
- free(E.filename); | |
- free(E.find_query); | |
- for (i=0; i<E.num_rows; ++i) | |
- editor_row_free(&E.row[i]); | |
-} | |
- | |
-/* set editor state variables, make room for status */ | |
-void | |
-init_editor() { | |
- E.cursor_x = 0; | |
- E.cursor_y = 0; | |
- E.cursor_rx = 0; | |
- E.mode = 0; | |
- E.num_rows = 0; | |
- atexit(deinit_editor); | |
- E.row = NULL; | |
- E.row_offset = 0; | |
- E.column_offset = 0; | |
- E.filename = NULL; | |
- E.status_msg[0] = '\0'; | |
- E.status_msg_time = 0; | |
- E.show_status = 0; | |
- E.file_changed = 0; | |
- E.find_query = NULL; | |
- | |
- if (get_window_size(&E.screen_rows, &E.screen_columns) == -1) | |
- die("get_window_size"); | |
- E.screen_rows -= 1; | |
-} | |
diff --git a/src/ve.h b/src/ve.h | |
t@@ -1,39 +0,0 @@ | |
-#ifndef BYOTE_H_ | |
-#define BYOTE_H_ | |
- | |
-#include <termios.h> | |
-#include <time.h> | |
- | |
-#define PROGNAME "ve" | |
-#define VERSION "0.0.1" | |
- | |
-#define TAB_WIDTH 4 | |
-#define STATUS_MESSAGE_TIMEOUT 3 | |
- | |
-/* editor row: stores a row of text */ | |
-typedef struct eRow { | |
- char *chars; /* text read from file */ | |
- char *rchars; /* text to render */ | |
- int size; /* length of chars */ | |
- int rsize; /* length of rchars */ | |
-} eRow; | |
- | |
-struct editor_config { | |
- int cursor_x, cursor_y, cursor_rx; | |
- int screen_rows, screen_columns; | |
- int num_rows; | |
- eRow *row; | |
- int row_offset, column_offset; | |
- char *filename; | |
- struct termios orig_termios; | |
- int mode; /* 0: normal, 1: insert, 2: visual */ | |
- int show_status; | |
- char status_msg[80]; | |
- time_t status_msg_time; | |
- int file_changed; | |
- char *find_query; | |
- int find_direction; | |
-}; | |
- | |
-extern struct editor_config E; | |
-#endif | |
diff --git a/ve.c b/ve.c | |
t@@ -0,0 +1,1062 @@ | |
+/* see LICENSE for license details */ | |
+ | |
+/* add feature test macro for getline and strdup compatibility */ | |
+#define _DEFAULT_SOURCE | |
+#define _BSD_SOURCE | |
+#define _GNU_SOURCE | |
+ | |
+#include <ctype.h> | |
+#include <errno.h> | |
+#include <fcntl.h> | |
+#include <stdarg.h> | |
+#include <stdlib.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+#include <sys/types.h> | |
+#include <sys/ioctl.h> | |
+#include <termios.h> | |
+#include <time.h> | |
+#include <unistd.h> | |
+ | |
+ | |
+/* macros */ | |
+#define PROGNAME "ve" | |
+#define VERSION "0.0.1" | |
+#define TAB_WIDTH 4 | |
+#define STATUS_MESSAGE_TIMEOUT 3 | |
+#define ABUF_INIT {NULL, 0} | |
+#define CTRL_KEY(k) ((k) & 0x1f) | |
+ | |
+ | |
+ | |
+/* types */ | |
+ | |
+struct abuf { | |
+ char *b; | |
+ int len; | |
+}; | |
+ | |
+/* editor row: stores a row of text */ | |
+typedef struct eRow { | |
+ char *chars; /* text read from file */ | |
+ char *rchars; /* text to render */ | |
+ int size; /* length of chars */ | |
+ int rsize; /* length of rchars */ | |
+} eRow; | |
+ | |
+struct editor_config { | |
+ int cursor_x, cursor_y, cursor_rx; | |
+ int screen_rows, screen_columns; | |
+ int num_rows; | |
+ eRow *row; | |
+ int row_offset, column_offset; | |
+ char *filename; | |
+ struct termios orig_termios; | |
+ int mode; /* 0: normal, 1: insert, 2: visual */ | |
+ int show_status; | |
+ char status_msg[80]; | |
+ time_t status_msg_time; | |
+ int file_changed; | |
+ char *find_query; | |
+ int find_direction; | |
+}; | |
+ | |
+ | |
+/* function declarations */ | |
+char* editor_prompt(char *prompt); | |
+void editor_set_status_message(const char *fmt, ...); | |
+ | |
+ | |
+/* global variables */ | |
+struct editor_config E; | |
+ | |
+ | |
+/* function definitions */ | |
+ | |
+void | |
+die(const char *s) | |
+{ | |
+ /* clear screen on exit */ | |
+ write(STDOUT_FILENO, "\x1b[2J", 4); | |
+ write(STDOUT_FILENO, "\x1b[H", 3); | |
+ perror(s); | |
+ exit(1); | |
+} | |
+ | |
+void | |
+disable_raw_mode() | |
+{ | |
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) | |
+ die("tcsetattr in disable_raw_mode()"); | |
+} | |
+ | |
+void | |
+enable_raw_mode() | |
+{ | |
+ struct termios raw; | |
+ | |
+ if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) | |
+ die("tcgetattr in enable_raw_mode()"); | |
+ atexit(disable_raw_mode); | |
+ | |
+ /* fix modifier keys, set 8 bits per char, ignore interrupts */ | |
+ raw = E.orig_termios; | |
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); | |
+ raw.c_oflag &= ~(OPOST); | |
+ raw.c_cflag |= (CS8); | |
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | |
+ | |
+ /* set read() timeout in tenths of seconds */ | |
+ raw.c_cc[VMIN] = 0; | |
+ raw.c_cc[VTIME] = 1; | |
+ | |
+ /* apply terminal settings */ | |
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) | |
+ die("tcsetattr in enable_raw_mode()"); | |
+} | |
+ | |
+char | |
+editor_read_key() | |
+{ | |
+ int nread; | |
+ char c; | |
+ while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { | |
+ if (nread == -1 && errno != EAGAIN) | |
+ die("read in editor_read_key()"); | |
+ } | |
+ return c; | |
+} | |
+ | |
+/* get screen size by moving cursor and get current position */ | |
+int | |
+get_cursor_position(int *rows, int *cols) { | |
+ char buf[32]; | |
+ unsigned int i; | |
+ | |
+ if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) | |
+ return -1; | |
+ | |
+ i = 0; | |
+ while (i < sizeof(buf) - 1) { | |
+ if (read(STDIN_FILENO, &buf[i], 1) != 1) | |
+ break; | |
+ if (buf[i] == 'R') | |
+ break; | |
+ ++i; | |
+ } | |
+ buf[i] = '\0'; | |
+ | |
+ if (buf[0] != '\x1b' || buf[1] != '[') | |
+ return -1; | |
+ if (scanf(&buf[2], "%d;%d", rows, cols) != 2) | |
+ return -1; | |
+ return 0; | |
+} | |
+ | |
+int | |
+get_window_size(int *rows, int *cols) | |
+{ | |
+ struct winsize ws; | |
+ | |
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { | |
+ /* fallback screen size detection */ | |
+ if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) | |
+ return -1; | |
+ return get_cursor_position(rows, cols); | |
+ } else { | |
+ *cols = ws.ws_col; | |
+ *rows = ws.ws_row; | |
+ return 0; | |
+ } | |
+} | |
+ | |
+ | |
+/* navigate over tab-representative string as one character */ | |
+int | |
+editor_row_cursor_x_to_rx(eRow *row, int cursor_x) | |
+{ | |
+ int rx, j; | |
+ rx = 0; | |
+ for (j=0; j<cursor_x; ++j) { | |
+ if (row->chars[j] == '\t') | |
+ rx += (TAB_WIDTH - 1) - (rx % TAB_WIDTH); | |
+ rx++; | |
+ } | |
+ return rx; | |
+} | |
+ | |
+/* translate on-screen position to data position */ | |
+int | |
+editor_row_cursor_rx_to_x(eRow *row, int cursor_rx) | |
+{ | |
+ int cur_rx, cx; | |
+ cur_rx = 0; | |
+ for (cx=0; cx<row->size; ++cx) { | |
+ if (row->chars[cx] == '\t') | |
+ cur_rx += (TAB_WIDTH - 1) - (cur_rx % TAB_WIDTH); | |
+ cur_rx++; | |
+ | |
+ if (cur_rx > cursor_rx) | |
+ return cx; | |
+ } | |
+ return cx; | |
+} | |
+ | |
+/* translate tabs before display */ | |
+void | |
+editor_row_update(eRow* row) | |
+{ | |
+ int j, idx, tabs; | |
+ | |
+ free(row->rchars); | |
+ row->rchars = malloc(row->size + 1); | |
+ | |
+ tabs = 0; | |
+ for (j=0; j<row->size; ++j) | |
+ if (row->chars[j] == '\t') | |
+ tabs++; | |
+ | |
+ free(row->rchars); | |
+ row->rchars = malloc(row->size + tabs*(TAB_WIDTH - 1) + 1); | |
+ | |
+ idx = 0; | |
+ for (j=0; j<row->size; ++j) { | |
+ if (row->chars[j] == '\t') { | |
+ row->rchars[idx++] = '>'; | |
+ while (idx % TAB_WIDTH != 0) | |
+ row->rchars[idx++] = ' '; | |
+ } else { | |
+ row->rchars[idx++] = row->chars[j]; | |
+ } | |
+ } | |
+ row->rchars[idx] = '\0'; | |
+ row->rsize = idx; | |
+} | |
+ | |
+/* add row to buffer */ | |
+void | |
+editor_row_insert(int i, char *s, size_t len) | |
+{ | |
+ if (i<0 || i>E.num_rows) | |
+ return; | |
+ | |
+ E.row = realloc(E.row, sizeof(eRow) * (E.num_rows + 1)); | |
+ memmove(&E.row[i+1], &E.row[i], sizeof(eRow) * (E.num_rows - i)); | |
+ | |
+ E.row[i].size = len; | |
+ E.row[i].chars = malloc(len + 1); | |
+ memcpy(E.row[i].chars, s, len); | |
+ E.row[i].chars[len] = '\0'; | |
+ | |
+ E.row[i].rsize = 0; | |
+ E.row[i].rchars = NULL; | |
+ editor_row_update(&E.row[i]); | |
+ | |
+ ++E.num_rows; | |
+ E.file_changed = 1; | |
+} | |
+ | |
+/* insert character to row, making sure to allocate memory accordingly */ | |
+void | |
+editor_row_insert_char(eRow *row, int i, int c) | |
+{ | |
+ if (i<0 || i>row->size) | |
+ i = row->size; | |
+ row->chars = realloc(row->chars, row->size + 2); | |
+ memmove(&row->chars[i+1], &row->chars[i], row->size - i+1); | |
+ row->size++; | |
+ row->chars[i] = c; | |
+ editor_row_update(row); | |
+ E.file_changed = 1; | |
+} | |
+ | |
+/* append a string to the end of a row */ | |
+void | |
+editor_row_append_string(eRow *row, char *s, size_t len) | |
+{ | |
+ row->chars = realloc(row->chars, row->size + len + 1); | |
+ memcpy(&row->chars[row->size], s, len); | |
+ row->size += len; | |
+ row->chars[row->size] = '\0'; | |
+ editor_row_update(row); | |
+ E.file_changed = 1; | |
+} | |
+ | |
+void | |
+editor_row_delete_char(eRow *row, int i) | |
+{ | |
+ if (i<0 || i>=row->size) | |
+ return; | |
+ memmove(&row->chars[i], &row->chars[i+1], row->size - i); | |
+ row->size--; | |
+ editor_row_update(row); | |
+ E.file_changed = 1; | |
+} | |
+ | |
+void | |
+editor_row_free(eRow *row) | |
+{ | |
+ free(row->rchars); | |
+ free(row->chars); | |
+} | |
+ | |
+void | |
+editor_row_delete(int i) | |
+{ | |
+ if (i<0 || i>=E.num_rows) | |
+ return; | |
+ editor_row_free(&E.row[i]); | |
+ memmove(&E.row[i], &E.row[i+1], sizeof(eRow)*(E.num_rows - i - 1)); | |
+ E.num_rows--; | |
+ E.file_changed = 1; | |
+} | |
+ | |
+void | |
+editor_insert_char(int c) | |
+{ | |
+ if (E.cursor_y == E.num_rows) | |
+ editor_row_insert(E.num_rows, "", 0); | |
+ editor_row_insert_char(&E.row[E.cursor_y], E.cursor_x, c); | |
+ E.cursor_x++; | |
+} | |
+ | |
+void | |
+editor_insert_new_line() | |
+{ | |
+ eRow *row; | |
+ if (E.cursor_x == 0) { | |
+ editor_row_insert(E.cursor_y, "", 0); | |
+ } else { | |
+ row = &E.row[E.cursor_y]; | |
+ editor_row_insert(E.cursor_y + 1, &row->chars[E.cursor_x], | |
+ row->size - E.cursor_x); | |
+ row = &E.row[E.cursor_y]; | |
+ row->size = E.cursor_x; | |
+ row->chars[row->size] = '\0'; | |
+ editor_row_update(row); | |
+ } | |
+ E.cursor_y++; | |
+ E.cursor_x = 0; | |
+} | |
+ | |
+/* delete a character before the cursor, and allow for deletion across rows */ | |
+void | |
+editor_delete_char_left() | |
+{ | |
+ eRow *row; | |
+ if (E.cursor_y == E.num_rows || (E.cursor_x == 0 && E.cursor_y == 0)) | |
+ return; | |
+ | |
+ row = &E.row[E.cursor_y]; | |
+ if (E.cursor_x > 0) { | |
+ editor_row_delete_char(row, E.cursor_x - 1); | |
+ E.cursor_x--; | |
+ } else { | |
+ E.cursor_x = E.row[E.cursor_y - 1].size; | |
+ editor_row_append_string(&E.row[E.cursor_y - 1], | |
+ row->chars, row->size); | |
+ editor_row_delete(E.cursor_y); | |
+ E.cursor_y--; | |
+ } | |
+} | |
+ | |
+void | |
+editor_delete_char_right() | |
+{ | |
+ if (E.cursor_y == E.num_rows) | |
+ return; | |
+ eRow *row = &E.row[E.cursor_y]; | |
+ editor_row_delete_char(row, E.cursor_x); | |
+} | |
+ | |
+void | |
+file_open(char *filename) | |
+{ | |
+ free(E.filename); | |
+ E.filename = strdup(filename); | |
+ | |
+ FILE *fp; | |
+ char *line; | |
+ size_t linecap; | |
+ ssize_t linelen; | |
+ | |
+ fp = fopen(filename, "r"); | |
+ if (!fp) | |
+ die("fopen in file_open"); | |
+ | |
+ line = NULL; | |
+ linecap = 0; | |
+ while ((linelen = getline(&line, &linecap, fp)) != -1) { | |
+ while (linelen > 0 && (line[linelen - 1] == '\n' || | |
+ line[linelen - 1] == '\r')) | |
+ linelen--; | |
+ editor_row_insert(E.num_rows, line, linelen); | |
+ } | |
+ free(line); | |
+ fclose(fp); | |
+ E.file_changed = 0; | |
+} | |
+ | |
+/* convert rows to one long char array of length buflen */ | |
+char* | |
+editor_concatenate_rows(int *buflen) | |
+{ | |
+ int totlen, j; | |
+ char *buf, *p; | |
+ | |
+ totlen = 0; | |
+ for (j=0; j<E.num_rows; ++j) | |
+ totlen += E.row[j].size + 1; /* add space for newline char */ | |
+ *buflen = totlen; | |
+ | |
+ buf = malloc(totlen); | |
+ p = buf; | |
+ for (j=0; j<E.num_rows; ++j) { | |
+ memcpy(p, E.row[j].chars, E.row[j].size); | |
+ p += E.row[j].size; | |
+ *p = '\n'; | |
+ p++; | |
+ } | |
+ return buf; | |
+} | |
+ | |
+void | |
+file_save(char *filename) | |
+{ | |
+ int len, fd; | |
+ char *buf; | |
+ | |
+ if (filename == NULL) { | |
+ filename = editor_prompt("save as: %s"); | |
+ if (filename == NULL) { | |
+ editor_set_status_message("save aborted"); | |
+ return; | |
+ } | |
+ } | |
+ | |
+ buf = editor_concatenate_rows(&len); | |
+ | |
+ fd = open(filename, O_RDWR | O_CREAT, 0644); | |
+ if (fd != -1) { | |
+ if (ftruncate(fd, len) != -1) { | |
+ if (write(fd, buf, len) == len) { | |
+ close(fd); | |
+ free(buf); | |
+ E.filename = filename; | |
+ E.file_changed = 0; | |
+ editor_set_status_message("%d bytes written to… | |
+ return; | |
+ } | |
+ } | |
+ close(fd); | |
+ } | |
+ free(buf); | |
+ editor_set_status_message("error: can't save! I/O error %s", | |
+ strerror(errno)); | |
+} | |
+ | |
+/* reverse of strstr (3) */ | |
+char* | |
+strrstr(char *haystack, char *needle, | |
+ size_t haystack_length, size_t needle_length) | |
+{ | |
+ char *cp; | |
+ for (cp = haystack + haystack_length - needle_length; | |
+ cp >= haystack; | |
+ cp--) { | |
+ if (strncmp(cp, needle, needle_length) == 0) | |
+ return cp; | |
+ } | |
+ return NULL; | |
+} | |
+ | |
+/* find E.find_query from current cursor position moving in E.direction | |
+ * if opposite_direction = 0, and opposite E.direction if | |
+ * opposite_direction = 1 */ | |
+void | |
+editor_find_next_occurence(int opposite_direction) | |
+{ | |
+ int y, y_inc, x_offset, query_len; | |
+ eRow *row; | |
+ char *match; | |
+ | |
+ if (!E.find_query) | |
+ return; | |
+ | |
+ if ((E.find_direction && !opposite_direction) || | |
+ (!E.find_direction && opposite_direction)) | |
+ y_inc = +1; | |
+ else | |
+ y_inc = -1; | |
+ | |
+ x_offset = 0; | |
+ query_len = strlen(E.find_query); | |
+ /* from cursor until end of document */ | |
+ for (y = E.cursor_y; | |
+ y != ((y_inc == +1) ? E.num_rows : -1); | |
+ y += y_inc) { | |
+ row = &E.row[y]; | |
+ | |
+ if (y == E.cursor_y) | |
+ x_offset = y_inc + E.cursor_x; | |
+ else | |
+ x_offset = 0; | |
+ | |
+ if (y_inc == +1) { | |
+ match = strstr(row->chars + x_offset, E.find_query); | |
+ if (match) | |
+ break; | |
+ } else { | |
+ match = strrstr(row->chars, E.find_query, | |
+ (y == E.cursor_y) ? E.cursor_x : row->… | |
+ query_len); | |
+ if (match) | |
+ break; | |
+ } | |
+ } | |
+ | |
+ if (match) { | |
+ E.cursor_y = y; | |
+ E.cursor_x = match - row->chars; | |
+ /* E.row_offset = E.num_rows; */ /* put line to top of screen … | |
+ | |
+ } else { | |
+ /* from other end of file until cursor */ | |
+ for (y = (y_inc == +1) ? 0 : E.num_rows - 1; | |
+ y != E.cursor_y + y_inc; | |
+ y += y_inc) { | |
+ row = &E.row[y]; | |
+ match = strstr(row->chars, E.find_query); | |
+ if (match) | |
+ break; | |
+ } | |
+ if (match) { | |
+ E.cursor_y = y; | |
+ E.cursor_x = editor_row_cursor_rx_to_x(row, match - ro… | |
+ } | |
+ } | |
+} | |
+ | |
+/* direction = 1 is forward, 0 is backward */ | |
+void | |
+editor_find(int direction) | |
+{ | |
+ char *query; | |
+ | |
+ if (direction) | |
+ query = editor_prompt("/%s"); | |
+ else | |
+ query = editor_prompt("?%s"); | |
+ if (query == NULL) | |
+ return; | |
+ | |
+ E.find_direction = direction; | |
+ free(E.find_query); | |
+ E.find_query = strdup(query); | |
+ editor_find_next_occurence(0); | |
+ free(query); | |
+} | |
+ | |
+ | |
+/* reallocate append buffer to hold string s */ | |
+void | |
+ab_append(struct abuf *ab, const char *s, int len) | |
+{ | |
+ char *new; | |
+ new = realloc(ab->b, ab->len + len); | |
+ | |
+ if (new == NULL) | |
+ return; | |
+ memcpy(&new[ab->len], s, len); | |
+ ab->b = new; | |
+ ab->len += len; | |
+} | |
+ | |
+void | |
+ab_free(struct abuf *ab) { | |
+ free(ab->b); | |
+} | |
+ | |
+int | |
+show_status_message() | |
+{ | |
+ if (E.show_status && | |
+ strlen(E.status_msg) && | |
+ time(NULL) - E.status_msg_time < STATUS_MESSAGE_TIMEOUT) { | |
+ return 1; | |
+ } else { | |
+ if (E.show_status) { | |
+ E.show_status = 0; | |
+ E.screen_rows++; | |
+ } | |
+ return 0; | |
+ } | |
+} | |
+ | |
+/* draw status line consisting of left and right components. | |
+ * if the screen is narrower than both parts, just show the left status, | |
+ * truncated, if necessary. */ | |
+void | |
+editor_draw_status(struct abuf *ab) | |
+{ | |
+ char left_status[512], right_status[512]; | |
+ int left_status_len, right_status_len, padding; | |
+ int percentage; | |
+ | |
+ left_status_len = 0; | |
+ switch (E.mode) { | |
+ case 1: | |
+ left_status_len = snprintf(left_status, sizeof(left_st… | |
+ "INSERT "); | |
+ break; | |
+ case 2: | |
+ left_status_len = snprintf(left_status, sizeof(left_st… | |
+ "VISUAL "); | |
+ break; | |
+ } | |
+ left_status_len += snprintf(left_status + left_status_len, | |
+ sizeof(left_status), | |
+ "%.20s %s", | |
+ E.filename ? E.filename : "[unnamed]", | |
+ E.file_changed ? "[+] " : ""); | |
+ | |
+ percentage = (int)((float)(E.cursor_y)/(E.num_rows-1)*100); | |
+ if (percentage < 0) percentage = 0; | |
+ right_status_len = snprintf(right_status, sizeof(right_status), | |
+ "%d%% < %d, %d", | |
+ percentage, | |
+ E.cursor_x+1, E.cursor_y+1); | |
+ | |
+ if (left_status_len > E.screen_columns) | |
+ left_status_len = E.screen_columns; | |
+ | |
+ padding = E.screen_columns - left_status_len - right_status_len; | |
+ if (padding < 0) { | |
+ if (left_status_len < E.screen_columns) | |
+ ab_append(ab, left_status, left_status_len); | |
+ else | |
+ ab_append(ab, left_status, E.screen_columns); | |
+ } else { | |
+ ab_append(ab, left_status, left_status_len); | |
+ while (padding--) | |
+ ab_append(ab, " ", 1); | |
+ ab_append(ab, right_status, right_status_len); | |
+ } | |
+ | |
+ if (show_status_message()) { | |
+ ab_append(ab, "\x1b[m", 3); | |
+ ab_append(ab, "\r\n", 2); | |
+ } | |
+} | |
+ | |
+/* draw status message if as long as it is specified and it is not too old */ | |
+void | |
+editor_draw_status_message(struct abuf *ab) | |
+{ | |
+ int msglen; | |
+ ab_append(ab, "\x1b[K", 3); | |
+ msglen = strlen(E.status_msg); | |
+ if (msglen > E.screen_columns) | |
+ msglen = E.screen_columns; | |
+ if (show_status_message()) | |
+ ab_append(ab, E.status_msg, msglen); | |
+} | |
+ | |
+/* set vertical offset between file and screen when hitting the boundaries */ | |
+void | |
+editor_scroll() | |
+{ | |
+ E.cursor_rx = 0; | |
+ if (E.cursor_y < E.num_rows) | |
+ E.cursor_rx = editor_row_cursor_x_to_rx(&E.row[E.cursor_y], | |
+ E.cursor_x); | |
+ | |
+ if (E.cursor_y < E.row_offset) | |
+ E.row_offset = E.cursor_y; | |
+ else if (E.cursor_y >= E.row_offset + E.screen_rows) | |
+ E.row_offset = E.cursor_y - E.screen_rows + 1; | |
+ | |
+ if (E.cursor_rx < E.column_offset) | |
+ E.column_offset = E.cursor_rx; | |
+ else if (E.cursor_rx >= E.column_offset + E.screen_columns) | |
+ E.column_offset = E.cursor_rx - E.screen_columns + 1; | |
+} | |
+ | |
+/* draw editor screen. | |
+ * show tilde characters after EOF, and show status on last line */ | |
+void | |
+editor_draw_rows(struct abuf *ab) | |
+{ | |
+ int y, len, file_row; | |
+ for (y = 0; y < E.screen_rows; ++y) { | |
+ file_row = y + E.row_offset; | |
+ if (file_row < E.num_rows) { | |
+ len = E.row[file_row].rsize - E.column_offset; | |
+ if (len < 0) | |
+ len = 0; | |
+ if (len > E.screen_columns) | |
+ len = E.screen_columns; | |
+ ab_append(ab, &E.row[file_row].rchars[E.column_offset]… | |
+ } else { | |
+ ab_append(ab, "~", 1); | |
+ } | |
+ | |
+ ab_append(ab, "\x1b[K", 3); /* erase to end of line */ | |
+ ab_append(ab, "\r\n", 2); | |
+ } | |
+} | |
+ | |
+/* fill output append buffer and write to terminal. | |
+ * move cursor to left before repaint, and to cursor position after. | |
+ * hide the cursor when repainting. | |
+ * VT100 escape sequences: | |
+ * - http://vt100.net/docs/vt100-ug/chapter3.html | |
+ * - http://vt100.net/docs/vt100-ug/chapter3.html#CUP */ | |
+void | |
+editor_refresh_screen() | |
+{ | |
+ char buf[32]; | |
+ struct abuf ab = ABUF_INIT; | |
+ | |
+ show_status_message(); | |
+ editor_scroll(); | |
+ | |
+ ab_append(&ab, "\x1b[?25l", 6); /* hide cursor */ | |
+ ab_append(&ab, "\x1b[H", 3); /* cursor to home */ | |
+ | |
+ editor_draw_rows(&ab); | |
+ editor_draw_status(&ab); | |
+ editor_draw_status_message(&ab); | |
+ | |
+ snprintf(buf, sizeof(buf), "\x1b[%d;%dH", | |
+ (E.cursor_y - E.row_offset)+1, | |
+ (E.cursor_rx - E.column_offset)+1); | |
+ ab_append(&ab, buf, strlen(buf)); | |
+ | |
+ ab_append(&ab, "\x1b[?25h", 6); /* show cursor */ | |
+ | |
+ write(STDOUT_FILENO, ab.b, ab.len); | |
+ ab_free(&ab); | |
+} | |
+ | |
+void | |
+editor_place_cursor(int cx, int cy) | |
+{ | |
+ char buf[128]; | |
+ snprintf(buf, sizeof(buf), "\x1b[%d;%dH", cy, cx); | |
+ write(STDOUT_FILENO, buf, strlen(buf)); | |
+} | |
+ | |
+/* set status message text, uses same format as printf */ | |
+void | |
+editor_set_status_message(const char *fmt, ...) | |
+{ | |
+ va_list ap; | |
+ va_start(ap, fmt); | |
+ vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap); | |
+ va_end(ap); | |
+ E.status_msg_time = time(NULL); | |
+ | |
+ if (!E.show_status) { | |
+ E.screen_rows--; | |
+ E.show_status = 1; | |
+ } | |
+} | |
+ | |
+/* prompt is expected to be a format string containing a %s */ | |
+char* | |
+editor_prompt(char *prompt) | |
+{ | |
+ size_t bufsize, buflen; | |
+ char *buf; | |
+ int c; | |
+ | |
+ bufsize = 128; | |
+ buflen = 0; | |
+ buf = malloc(bufsize); | |
+ buf[0] = '\0'; | |
+ | |
+ while (1) { | |
+ editor_set_status_message(prompt, buf); | |
+ editor_refresh_screen(); | |
+ editor_place_cursor(strlen(prompt) - 1 + strlen(buf), | |
+ E.screen_rows + 2); | |
+ | |
+ c = editor_read_key(); | |
+ if (c == CTRL_KEY('h') || c == 127) { /* detect backspace */ | |
+ if (buflen != 0) | |
+ buf[--buflen] = '\0'; | |
+ } else if (c == '\x1b' || (c == '\r' && !buflen)) { /* detect … | |
+ editor_set_status_message(""); | |
+ free(buf); | |
+ return NULL; | |
+ } else if (c == '\r') { | |
+ if (buflen != 0) { | |
+ editor_set_status_message(""); | |
+ return buf; | |
+ } | |
+ } else if (!iscntrl(c) && c < 128) { | |
+ if (buflen >= bufsize - 1) { | |
+ bufsize *= 2; | |
+ buf = realloc(buf, bufsize); | |
+ } | |
+ buf[buflen++] = c; | |
+ buf[buflen] = '\0'; | |
+ } | |
+ } | |
+} | |
+ | |
+/* move cursor according to screen, file, and line limits */ | |
+void | |
+editor_move_cursor(char key) | |
+{ | |
+ int row_len; | |
+ eRow *row; | |
+ | |
+ switch(key) { | |
+ case 'h': | |
+ if (E.cursor_x != 0) { | |
+ E.cursor_x--; | |
+ } else if (E.cursor_y > 0) { | |
+ E.cursor_y--; | |
+ E.cursor_x = E.row[E.cursor_y].size; | |
+ } | |
+ break; | |
+ case 'j': | |
+ if (E.cursor_y < E.num_rows - 1) | |
+ E.cursor_y++; | |
+ break; | |
+ case 'k': | |
+ if (E.cursor_y != 0) | |
+ E.cursor_y--; | |
+ break; | |
+ case 'l': | |
+ row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cur… | |
+ if (row && E.cursor_x < row->size - 1) { | |
+ E.cursor_x++; | |
+ } else if (row && E.cursor_x == row->size && | |
+ E.cursor_y < E.num_rows - 1) { | |
+ E.cursor_y++; | |
+ E.cursor_x = 0; | |
+ } | |
+ break; | |
+ } | |
+ | |
+ /* do not allow navigation past EOL by vertical navigation */ | |
+ row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cursor_y]; | |
+ row_len = row ? row->size : 0; | |
+ if (E.cursor_x > row_len) | |
+ E.cursor_x = row_len; | |
+} | |
+ | |
+void | |
+editor_process_keypress() | |
+{ | |
+ char c; | |
+ int i; | |
+ | |
+ c = editor_read_key(); | |
+ | |
+ if (E.mode == 0) { /* normal mode */ | |
+ switch (c) { | |
+ case 'i': | |
+ E.mode = 1; | |
+ break; | |
+ case 'a': | |
+ editor_move_cursor('l'); | |
+ E.mode = 1; | |
+ break; | |
+ | |
+ case CTRL_KEY('q'): | |
+ if (E.file_changed) { | |
+ editor_set_status_message("error: " | |
+ "file ha… | |
+ "Press C… | |
+ break; | |
+ } else { | |
+ write(STDOUT_FILENO, "\x1b[2J", 4); /*… | |
+ write(STDOUT_FILENO, "\x1b[H", 3); | |
+ exit(0); | |
+ break; | |
+ } | |
+ case CTRL_KEY('x'): | |
+ write(STDOUT_FILENO, "\x1b[2J", 4); /* clear s… | |
+ write(STDOUT_FILENO, "\x1b[H", 3); | |
+ exit(0); | |
+ break; | |
+ case 'h': | |
+ case 'j': | |
+ case 'k': | |
+ case 'l': | |
+ editor_move_cursor(c); | |
+ break; | |
+ | |
+ case CTRL_KEY('w'): | |
+ file_save(E.filename); | |
+ break; | |
+ | |
+ case CTRL_KEY('f'): | |
+ i = E.screen_rows; | |
+ while (i--) | |
+ editor_move_cursor('j'); | |
+ break; | |
+ case CTRL_KEY('b'): | |
+ i = E.screen_rows; | |
+ while (i--) | |
+ editor_move_cursor('k'); | |
+ break; | |
+ | |
+ case CTRL_KEY('d'): | |
+ i = E.screen_rows/2; | |
+ while (i--) | |
+ editor_move_cursor('j'); | |
+ break; | |
+ case CTRL_KEY('u'): | |
+ i = E.screen_rows/2; | |
+ while (i--) | |
+ editor_move_cursor('k'); | |
+ break; | |
+ | |
+ case '0': | |
+ E.cursor_x = 0; | |
+ break; | |
+ case '$': | |
+ if (E.cursor_y < E.num_rows) | |
+ E.cursor_x = E.row[E.cursor_y].size - … | |
+ break; | |
+ | |
+ case 'g': | |
+ E.cursor_x = 0; | |
+ E.cursor_y = 0; | |
+ break; | |
+ case 'G': | |
+ E.cursor_x = 0; | |
+ E.cursor_y = E.num_rows - 1; | |
+ break; | |
+ | |
+ case 'x': | |
+ editor_delete_char_right(); | |
+ break; | |
+ case 'd': | |
+ editor_row_delete(E.cursor_y); | |
+ editor_move_cursor('h'); | |
+ break; | |
+ | |
+ case 'o': | |
+ if (E.cursor_y < E.num_rows) | |
+ E.cursor_x = E.row[E.cursor_y].size; | |
+ editor_insert_new_line(); | |
+ E.mode = 1; | |
+ break; | |
+ case 'O': | |
+ E.cursor_x = 0; | |
+ editor_insert_new_line(); | |
+ editor_move_cursor('k'); | |
+ E.mode = 1; | |
+ break; | |
+ | |
+ case 'I': | |
+ E.cursor_x = 0; | |
+ E.mode = 1; | |
+ break; | |
+ case 'A': | |
+ if (E.cursor_y < E.num_rows) | |
+ E.cursor_x = E.row[E.cursor_y].size; | |
+ E.mode = 1; | |
+ break; | |
+ | |
+ case '/': | |
+ editor_find(1); | |
+ break; | |
+ case '?': | |
+ editor_find(0); | |
+ break; | |
+ case 'n': | |
+ editor_find_next_occurence(0); | |
+ break; | |
+ case 'N': | |
+ editor_find_next_occurence(1); | |
+ break; | |
+ } | |
+ } else if (E.mode == 1) { /* insert mode */ | |
+ switch (c) { | |
+ case CTRL_KEY('c'): | |
+ case '\x1b': /* escape */ | |
+ E.mode = 0; | |
+ break; | |
+ | |
+ case CTRL_KEY('\r'): /* enter */ | |
+ editor_insert_new_line(); | |
+ break; | |
+ | |
+ case 127: /* backspace */ | |
+ case CTRL_KEY('h'): | |
+ editor_delete_char_left(); | |
+ break; | |
+ | |
+ case CTRL_KEY('l'): | |
+ break; | |
+ | |
+ default: | |
+ editor_insert_char(c); | |
+ break; | |
+ } | |
+ } | |
+} | |
+ | |
+void | |
+deinit_editor() { | |
+ int i; | |
+ free(E.filename); | |
+ free(E.find_query); | |
+ for (i=0; i<E.num_rows; ++i) | |
+ editor_row_free(&E.row[i]); | |
+} | |
+ | |
+/* set editor state variables, make room for status */ | |
+void | |
+init_editor() { | |
+ E.cursor_x = 0; | |
+ E.cursor_y = 0; | |
+ E.cursor_rx = 0; | |
+ E.mode = 0; | |
+ E.num_rows = 0; | |
+ atexit(deinit_editor); | |
+ E.row = NULL; | |
+ E.row_offset = 0; | |
+ E.column_offset = 0; | |
+ E.filename = NULL; | |
+ E.status_msg[0] = '\0'; | |
+ E.status_msg_time = 0; | |
+ E.show_status = 0; | |
+ E.file_changed = 0; | |
+ E.find_query = NULL; | |
+ | |
+ if (get_window_size(&E.screen_rows, &E.screen_columns) == -1) | |
+ die("get_window_size"); | |
+ E.screen_rows -= 1; | |
+} | |
+ | |
+ | |
+/* main */ | |
+int | |
+main(int argc, char* argv[]) | |
+{ | |
+ enable_raw_mode(); | |
+ init_editor(); | |
+ | |
+ /* TODO: proper argument handling */ | |
+ if (argc >= 2) { | |
+ file_open(argv[1]); | |
+ } | |
+ | |
+ editor_set_status_message("%s v%s", PROGNAME, VERSION); | |
+ | |
+ while (1) { | |
+ editor_refresh_screen(); | |
+ editor_process_keypress(); | |
+ } | |
+ return 0; | |
+} | |
+ |