tMerge source into single file - ve - a minimal text editor (work in progress)
(HTM) git clone git://src.adamsgaard.dk/ve
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
(DIR) commit 7a8cfc0fc10085e622932f958235613b9ca8e149
(DIR) parent 0c629a2638b242c02e9dfce571b6738d64727b34
(HTM) Author: Anders Damsgaard <anders@adamsgaard.dk>
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(-)
---
(DIR) 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
(DIR) 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);
-}
(DIR) 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
(DIR) 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->size,
- 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 - row->chars);
- }
- }
-}
-
-/* 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);
-}
(DIR) 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
(DIR) 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 escape */
- 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.cursor_y];
- 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 has unsaved changes. "
- "Press C-x to confirm quit");
- break;
- } else {
- write(STDOUT_FILENO, "\x1b[2J", 4); /* clear screen */
- write(STDOUT_FILENO, "\x1b[H", 3);
- exit(0);
- break;
- }
- case CTRL_KEY('x'):
- write(STDOUT_FILENO, "\x1b[2J", 4); /* clear screen */
- 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 - 1;
- 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;
- }
- }
-}
(DIR) 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
(DIR) 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 disk", len);
- return;
- }
- }
- close(fd);
- }
- free(buf);
- editor_set_status_message("error: can't save! I/O error %s",
- strerror(errno));
-}
(DIR) 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
(DIR) 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;
-}
(DIR) 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_status),
- "INSERT ");
- break;
- case 2:
- left_status_len = snprintf(left_status, sizeof(left_status),
- "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], len);
- } 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;
- }
-}
(DIR) 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
(DIR) 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;
-}
(DIR) 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
(DIR) 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;
- }
-}
(DIR) 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
(DIR) 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;
-}
(DIR) 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
(DIR) 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 disk", len);
+ 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->size,
+ 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 - row->chars);
+ }
+ }
+}
+
+/* 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_status),
+ "INSERT ");
+ break;
+ case 2:
+ left_status_len = snprintf(left_status, sizeof(left_status),
+ "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], len);
+ } 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 escape */
+ 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.cursor_y];
+ 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 has unsaved changes. "
+ "Press C-x to confirm quit");
+ break;
+ } else {
+ write(STDOUT_FILENO, "\x1b[2J", 4); /* clear screen */
+ write(STDOUT_FILENO, "\x1b[H", 3);
+ exit(0);
+ break;
+ }
+ case CTRL_KEY('x'):
+ write(STDOUT_FILENO, "\x1b[2J", 4); /* clear screen */
+ 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 - 1;
+ 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;
+}
+