diff options
| author | Marc André Tanner <mat@brain-dump.org> | 2014-08-24 10:04:28 +0200 |
|---|---|---|
| committer | Marc André Tanner <mat@brain-dump.org> | 2014-08-24 10:05:37 +0200 |
| commit | bc0f09dce9fb9420ea1d5c10ebfacf50916b10af (patch) | |
| tree | fdd6c4d7c791bcb540c11508b5007721f9c10c7f /vis.c | |
| parent | 6f8cd49d22650ea3c91f0b96b2b576104fd0670c (diff) | |
| download | vis-bc0f09dce9fb9420ea1d5c10ebfacf50916b10af.tar.gz vis-bc0f09dce9fb9420ea1d5c10ebfacf50916b10af.tar.xz | |
Add work in progress editor frontend
Diffstat (limited to 'vis.c')
| -rw-r--r-- | vis.c | 264 |
1 files changed, 264 insertions, 0 deletions
@@ -0,0 +1,264 @@ +#define _POSIX_SOURCE +#include <locale.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <sys/select.h> +#include <sys/ioctl.h> + +#include "editor.h" +#include "util.h" + +#ifdef PDCURSES +int ESCDELAY; +#endif +#ifndef NCURSES_REENTRANT +# define set_escdelay(d) (ESCDELAY = (d)) +#endif + +typedef union { + size_t i; + const char *s; + size_t (*m)(Editor*); + void (*f)(Editor*); +} Arg; + +typedef struct { + char str[6]; + int code; +} Key; + +typedef struct { + Key key[2]; + void (*func)(const Arg *arg); + const Arg arg; +} KeyBinding; + +typedef struct Mode Mode; +struct Mode { + Mode *parent; + KeyBinding *bindings; + const char *name; + void (*enter)(void); + void (*leave)(void); + bool (*input)(const char *str, size_t len); +}; + +typedef struct { + char *name; + Mode *mode; +} Config; + +static void cursor(const Arg *arg); +static void call(const Arg *arg); +static void insert(const Arg *arg); +static void line(const Arg *arg); +static void find_forward(const Arg *arg); +static void find_backward(const Arg *arg); + +static Mode *mode; +static Editor *editor; + +#include "config.h" + +static Config *config = &editors[0]; + +static void cursor(const Arg *arg) { + arg->m(editor); +} + +static void call(const Arg *arg) { + arg->f(editor); +} + +static void line(const Arg *arg) { + editor_line_goto(editor, arg->i); +} + +static void find_forward(const Arg *arg) { + editor_search(editor, arg->s, 1); +} + +static void find_backward(const Arg *arg) { + editor_search(editor, arg->s, -1); +} + +static void insert(const Arg *arg) { + editor_insert(editor, arg->s, strlen(arg->s)); +} + +typedef struct Screen Screen; +static struct Screen { + int w, h; + bool need_resize; +} screen = { .need_resize = true }; + +static void sigwinch_handler(int sig) { + screen.need_resize = true; +} + +static void resize_screen(Screen *screen) { + struct winsize ws; + + if (ioctl(0, TIOCGWINSZ, &ws) == -1) { + getmaxyx(stdscr, screen->h, screen->w); + } else { + screen->w = ws.ws_col; + screen->h = ws.ws_row; + } + + resizeterm(screen->h, screen->w); + wresize(stdscr, screen->h, screen->w); + screen->need_resize = false; +} + +static void setup() { + setlocale(LC_CTYPE, ""); + if (!getenv("ESCDELAY")) + set_escdelay(50); + initscr(); + start_color(); + raw(); + noecho(); + keypad(stdscr, TRUE); + meta(stdscr, TRUE); + resize_screen(&screen); + /* needed because we use getch() which implicitly calls refresh() which + would clear the screen (overwrite it with an empty / unused stdscr */ + refresh(); + + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = sigwinch_handler; + sigaction(SIGWINCH, &sa, NULL); +} + +static void cleanup() { + editor_free(editor); + endwin(); +} + +static bool keymatch(Key *key0, Key *key1) { + return (key0->str[0] && memcmp(key0->str, key1->str, sizeof(key1->str)) == 0) || + (key0->code && key0->code == key1->code); +} + +static KeyBinding *keybinding(Mode *mode, Key *key0, Key *key1) { + for (; mode; mode = mode->parent) { + for (KeyBinding *kb = mode->bindings; kb && (kb->key[0].code || kb->key[0].str[0]); kb++) { + if (keymatch(key0, &kb->key[0]) && (!key1 || keymatch(key1, &kb->key[1]))) + return kb; + } + } + return NULL; +} + +int main(int argc, char *argv[]) { + /* decide which key configuration to use based on argv[0] */ + char *arg0 = argv[0]; + while (*arg0 && (*arg0 == '.' || *arg0 == '/')) + arg0++; + for (int i = 0; i < LENGTH(editors); i++) { + if (editors[i].name[0] == arg0[0]) { + config = &editors[i]; + break; + } + } + mode = config->mode; + + setup(); + if (!(editor = editor_new(screen.w, screen.h, statusbar))) + return 1; + if (!editor_syntax_load(editor, syntaxes, colors)) + return 1; + char *filename = argc > 1 ? argv[1] : NULL; + if (!editor_load(editor, filename)) + return 1; + + struct timeval idle = { .tv_usec = 0 }, *timeout = NULL; + Key key, key_prev, *key_mod = NULL; + + for (;;) { + if (screen.need_resize) { + resize_screen(&screen); + editor_resize(editor, screen.w, screen.h); + } + + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + editor_update(editor); + doupdate(); + idle.tv_sec = 3; + int r = select(1, &fds, NULL, NULL, timeout); + if (r == -1 && errno == EINTR) + continue; + + if (r < 0) { + perror("select()"); + exit(EXIT_FAILURE); + } + + if (!FD_ISSET(STDIN_FILENO, &fds)) { + editor_snapshot(editor); + timeout = NULL; + continue; + } + + int keycode = getch(); + if (keycode == ERR) + continue; + + // TODO verbatim insert mode + int len = 0; + if (keycode >= KEY_MIN) { + key.code = keycode; + key.str[0] = '\0'; + } else { + char keychar = keycode; + key.str[len++] = keychar; + key.code = 0; + + if (!ISASCII(keychar) || keychar == '\e') { + nodelay(stdscr, TRUE); + for (int t; len < LENGTH(key.str) && (t = getch()) != ERR; len++) + key.str[len] = t; + nodelay(stdscr, FALSE); + } + } + + for (size_t i = len; i < LENGTH(key.str); i++) + key.str[i] = '\0'; + + KeyBinding *action = keybinding(mode, key_mod ? key_mod : &key, key_mod ? &key : NULL); + if (!action && key_mod) { + /* second char of a combination was invalid, search again without the prefix */ + action = keybinding(mode, &key, NULL); + key_mod = NULL; + } + if (action) { + /* check if it is the first part of a combination */ + if (!key_mod && (action->key[1].code || action->key[1].str[0])) { + key_prev = key; + key_mod = &key_prev; + continue; + } + action->func(&action->arg); + key_mod = NULL; + continue; + } + + if (keycode >= KEY_MIN) + continue; + + if (mode->input && mode->input(key.str, len)) + timeout = &idle; + } + + cleanup(); + return 0; +} |
