diff --git a/ke/Makefile b/ke/Makefile index e608d82..1154a77 100644 --- a/ke/Makefile +++ b/ke/Makefile @@ -1,9 +1,5 @@ BIN := ke -OBJS := main.o \ - display.o \ - keys.o \ - terminal.o \ - util.o +OBJS := main.o LDFLAGS := -static CFLAGS := -pedantic -Wall -Werror -Wextra -O2 -std=c99 diff --git a/ke/escapes.txt b/ke/escapes.txt new file mode 100644 index 0000000..4320b78 --- /dev/null +++ b/ke/escapes.txt @@ -0,0 +1,13 @@ +ESCAPE CODES +============ + +note: these will all omit the leading ESC[ +https://vt100.net/docs/vt100-ug/chapter3.html + +H reposition cursor @ 1,1 +${row};${col}H reposition cursor @ ${row},${col} +J erase-in-display +h set mode +l reset mode + modes: + ?25 hide cursor feature diff --git a/ke/main.c b/ke/main.c index 388c690..9b2ee7e 100644 --- a/ke/main.c +++ b/ke/main.c @@ -1,31 +1,280 @@ +#include #include #include #include #include +#include #include #include -#include "display.h" #include "keys.h" #include "terminal.h" #include "util.h" +#define ESCSEQ "\x1b[" +#define CTRL_KEY(key) ((key)&0x1f) + +/* + * define the keyboard input modes + * normal: no special mode + * kcommand: ^k commands + */ +#define MODE_NORMAL 0 +#define MODE_KCOMMAND 1 + + +struct { + struct termios entry_term; + int rows, cols; +} editor; + + +/* append buffer */ +struct abuf { + char *b; + int len; +}; +#define ABUF_INIT {NULL, 0} + + +void +ab_append(struct abuf *buf, const char *s, int len) +{ + char *nc = realloc(buf->b, buf->len + len); + + if (nc == NULL) { + return; + } + + memcpy(&nc[buf->len], s, len); + buf->b = nc; + buf->len += len; /* DANGER: overflow */ +} + + +void +ab_free(struct abuf *buf) +{ + free(buf->b); +} + + +void +die(const char *s) +{ + /* + * NOTE(kyle): this is a duplication of the code in display.c + * but I would like to be able to import these files from there. + */ + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); + + perror(s); + exit(1); +} + + +/* + * get_winsz uses the TIOCGWINSZ to get the window size. + * + * there's a fallback way to do this, too, that involves moving the + * cursor down and to the left \x1b[999C\x1b[999B. I'm going to skip + * on this for now because it's bloaty and this works on OpenBSD and + * Linux, at least. + */ +int +get_winsz(int *rows, int *cols) +{ + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || + ws.ws_col == 0) { + return -1; + } + + *cols = ws.ws_col; + *rows = ws.ws_row; + return 0; +} + + +static char +get_keypress() +{ + char c = 0; + + if (read(STDIN_FILENO, &c, 1) == -1) { + die("get_keypress:read"); + } + + return c; +} + + +void +process_keypress() +{ + static int m = MODE_NORMAL; + char c = get_keypress(); + + if (c == 0x0) { + return; + } + + switch (m) { + case MODE_KCOMMAND: + switch (c) { + case 'q': + case CTRL_KEY('q'): + exit(0); + } + + m = MODE_NORMAL; + return; + } + + if (c == CTRL_KEY('k')) { + m = MODE_KCOMMAND; + return; + } +} + + +/* + * A text editor needs the terminal to be in raw mode; but the default + * is to be in canonical (cooked) mode, which is a buffered input mode. + */ + +static void +enable_termraw() +{ + struct termios raw; + + /* Read the current terminal parameters for standard input. */ + if (tcgetattr(STDIN_FILENO, &raw) == -1) { + die("tcgetattr while enabling raw mode"); + } + + /* + * Put the terminal into raw mode. + */ + cfmakeraw(&raw); + + /* + * Set timeout for read(2). + * + * VMIN: what is the minimum number of bytes required for read + * to return? + * + * VTIME: max time before read(2) returns in hundreds of milli- + * seconds. + */ + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + + /* + * Now write the terminal parameters to the current terminal, + * after flushing any waiting input out. + */ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { + die("tcsetattr while enabling raw mode"); + } +} + + +static void +disable_termraw() +{ + display_clear(NULL); + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &editor.entry_term) == -1) { + die("couldn't disable terminal raw mode"); + } +} + + +void +setup_terminal() +{ + if (tcgetattr(STDIN_FILENO, &editor.entry_term) == -1) { + die("can't snapshot terminal settings"); + } + atexit(disable_termraw); + enable_termraw(); +} + + +void +display_clear(struct abuf *ab) +{ + if (ab == NULL) { + write(STDOUT_FILENO, ESCSEQ "2J", 4); + write(STDOUT_FILENO, ESCSEQ "H", 3); + } else { + ab_append(ab, ESCSEQ "2J", 4); + ab_append(ab, ESCSEQ "H", 3); + } +} + + +void +draw_rows(struct abuf *ab) +{ + int y; + + for (y = 0; y < editor.rows-1; y++) { + ab_append(ab, "|\r\n", 3); + } + ab_append(ab, "|", 1); +} + + +void +display_refresh() +{ + struct abuf ab = ABUF_INIT; + + ab_append(&ab, ESCSEQ "?25l", 6); + display_clear(&ab); + draw_rows(&ab); + ab_append(&ab, ESCSEQ "1;2H", 7); + ab_append(&ab, ESCSEQ "?25h", 6); + + write(STDOUT_FILENO, ab.b, ab.len); + ab_free(&ab); +} + + void loop() { while (1) { + display_refresh(); process_keypress(); } } +/* + * init_editor should set up the global editor struct. + */ +void +init_editor() +{ + if (get_winsz(&editor.rows, &editor.cols) == -1) { + die("can't get window size"); + } +} + + int main() { setup_terminal(); + init_editor(); - display_clear(); + display_clear(NULL); loop(); return 0; diff --git a/ke/terminal.c b/ke/terminal.c deleted file mode 100644 index d573420..0000000 --- a/ke/terminal.c +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include - -#include "util.h" - - -#define ESCSEQ "\x1b[" - - -static struct termios entry_term; - - -/* - * A text editor needs the terminal to be in raw mode; but the default - * is to be in canonical (cooked) mode, which is a buffered input mode. - */ - -static void -enable_termraw() -{ - struct termios raw; - - /* Read the current terminal parameters for standard input. */ - if (tcgetattr(STDIN_FILENO, &raw) == -1) { - die("tcgetattr while enabling raw mode"); - } - - /* - * Put the terminal into raw mode. - */ - cfmakeraw(&raw); - - /* - * Set timeout for read(2). - * - * VMIN: what is the minimum number of bytes required for read - * to return? - * - * VTIME: max time before read(2) returns in hundreds of milli- - * seconds. - */ - raw.c_cc[VMIN] = 0; - raw.c_cc[VTIME] = 1; - - /* - * Now write the terminal parameters to the current terminal, - * after flushing any waiting input out. - */ - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { - die("tcsetattr while enabling raw mode"); - } -} - - -static void -disable_termraw() -{ - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &entry_term) == -1) { - die("couldn't disable terminal raw mode"); - } -} - - -void -setup_terminal() -{ - if (tcgetattr(STDIN_FILENO, &entry_term) == -1) { - die("can't snapshot terminal settings"); - } - atexit(disable_termraw); - enable_termraw(); -} - - -void -display_clear() -{ - write(STDOUT_FILENO, ESCSEQ "2J", 4); - write(STDOUT_FILENO, ESCSEQ "H", 3); -} diff --git a/ke/terminal.h b/ke/terminal.h deleted file mode 100644 index 1bb47d9..0000000 --- a/ke/terminal.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef KE_TERMINAL_H -#define KE_TERMINAL_H - - -void setup_terminal(); -void display_clear(); - - -#endif /* KE_TERMINAL_H */ diff --git a/ke/util.c b/ke/util.c deleted file mode 100644 index 1f13abf..0000000 --- a/ke/util.c +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include - - -void -die(const char *s) -{ - /* - * NOTE(kyle): this is a duplication of the code in display.c - * but I would like to be able to import these files from there. - */ - write(STDOUT_FILENO, "\x1b[2J", 4); - write(STDOUT_FILENO, "\x1b[H", 3); - - perror(s); - exit(1); -} diff --git a/ke/util.h b/ke/util.h deleted file mode 100644 index f27925b..0000000 --- a/ke/util.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef KE_UTIL_H -#define KE_UTIL_H - - -void die(const char *s); - - -#endif /* KE_UTIL_H */