From 78e0601e4ee68177ef597e75d08da786b0cabd3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Andr=C3=A9=20Tanner?= Date: Tue, 9 Sep 2014 21:21:30 +0200 Subject: Rename vis.[ch] to editor.[ch] and main.c to vis.c --- Makefile | 4 +- colors.c | 12 +- config.def.h | 24 +- editor.c | 483 ++++++++++++++++++++++ editor.h | 166 ++++++++ main.c | 1002 -------------------------------------------- util.h | 5 + vis.c | 1301 ++++++++++++++++++++++++++++++++++++++++------------------ vis.h | 166 -------- window.c | 2 +- window.h | 4 +- 11 files changed, 1587 insertions(+), 1582 deletions(-) create mode 100644 editor.c create mode 100644 editor.h delete mode 100644 main.c delete mode 100644 vis.h diff --git a/Makefile b/Makefile index c859fde..85725d7 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ include config.mk -SRC = vis.c window.c text.c text-motions.c text-objects.c register.c +SRC = editor.c window.c text.c text-motions.c text-objects.c register.c HDR := ${SRC:.c=.h} util.h config.def.h -SRC += main.c colors.c +SRC += vis.c colors.c OBJ = ${SRC:.c=.o} ALL = ${SRC} ${HDR} config.mk Makefile LICENSE README vis.1 diff --git a/colors.c b/colors.c index 4a447f6..d1a0357 100644 --- a/colors.c +++ b/colors.c @@ -2,7 +2,7 @@ #include #include -#include "vis.h" +#include "editor.h" #include "util.h" #ifdef NCURSES_VERSION @@ -30,7 +30,7 @@ static unsigned int color_hash(short fg, short bg) return fg * (COLORS + 2) + bg; } -short vis_color_get(short fg, short bg) +short editor_color_get(short fg, short bg) { if (fg >= COLORS) fg = default_fg; @@ -68,10 +68,10 @@ short vis_color_get(short fg, short bg) return color_pair >= 0 ? color_pair : -color_pair; } -short vis_color_reserve(short fg, short bg) +short editor_color_reserve(short fg, short bg) { if (!color2palette) - vis_init(); + editor_init(); if (!color2palette || fg >= COLORS || bg >= COLORS) return 0; if (!has_default_colors && fg == -1) @@ -89,7 +89,7 @@ short vis_color_reserve(short fg, short bg) return color_pair >= 0 ? color_pair : -color_pair; } -void vis_init(void) +void editor_init(void) { if (color2palette) return; @@ -102,5 +102,5 @@ void vis_init(void) color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS); if (COLORS) color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short)); - vis_color_reserve(COLOR_WHITE, COLOR_BLACK); + editor_color_reserve(COLOR_WHITE, COLOR_BLACK); } diff --git a/config.def.h b/config.def.h index 3b17d88..0bc0650 100644 --- a/config.def.h +++ b/config.def.h @@ -334,20 +334,20 @@ static KeyBinding vis_marks_set[] = { static KeyBinding vis_normal[] = { { { CONTROL('w'), NONE('c') }, split, { .s = NULL } }, - { { CONTROL('w'), NONE('j') }, call, { .f = vis_window_next } }, - { { CONTROL('w'), NONE('k') }, call, { .f = vis_window_prev } }, + { { CONTROL('w'), NONE('j') }, call, { .f = editor_window_next } }, + { { CONTROL('w'), NONE('k') }, call, { .f = editor_window_prev } }, { { CONTROL('F') }, cursor, { .m = window_page_up } }, { { CONTROL('B') }, cursor, { .m = window_page_down } }, { { NONE('.') }, repeat, { } }, { { NONE('n') }, movement, { .i = MOVE_SEARCH_FORWARD } }, { { NONE('N') }, movement, { .i = MOVE_SEARCH_BACKWARD } }, - { { NONE('x') }, call, { .f = vis_delete_key } }, + { { NONE('x') }, call, { .f = editor_delete_key } }, { { NONE('i') }, switchmode, { .i = VIS_MODE_INSERT } }, { { NONE('v') }, switchmode, { .i = VIS_MODE_VISUAL } }, { { NONE('R') }, switchmode, { .i = VIS_MODE_REPLACE} }, { { NONE('u') }, undo, { NULL } }, { { CONTROL('R') }, redo, { NULL } }, - { { CONTROL('L') }, call, { .f = vis_draw } }, + { { CONTROL('L') }, call, { .f = editor_draw } }, { { NONE(':') }, prompt, { .s = ":" } }, { /* empty last element, array terminator */ }, }; @@ -377,8 +377,8 @@ static void vis_visual_leave(Mode *new) { static KeyBinding vis_readline_mode[] = { { { NONE(ESC) }, switchmode, { .i = VIS_MODE_NORMAL } }, { { CONTROL('c') }, switchmode, { .i = VIS_MODE_NORMAL } }, - BACKSPACE( call, f, vis_backspace_key ), - { { CONTROL('D') }, call, { .f = vis_delete_key } }, + BACKSPACE( call, f, editor_backspace_key ), + { { CONTROL('D') }, call, { .f = editor_delete_key } }, { { CONTROL('W') }, delete_word, { NULL } }, { /* empty last element, array terminator */ }, }; @@ -395,7 +395,7 @@ static KeyBinding vis_prompt_mode[] = { static void vis_prompt_leave(Mode *new) { if (new != &vis_modes[VIS_MODE_OPERATOR]) - vis_prompt_hide(vis); + editor_prompt_hide(vis); } static KeyBinding vis_insert_register_mode[] = { @@ -444,7 +444,7 @@ static void vis_insert_idle(void) { } static void vis_insert_input(const char *str, size_t len) { - vis_insert_key(vis, str, len); + editor_insert_key(vis, str, len); } static KeyBinding vis_replace[] = { @@ -453,7 +453,7 @@ static KeyBinding vis_replace[] = { }; static void vis_replace_input(const char *str, size_t len) { - vis_replace_key(vis, str, len); + editor_replace_key(vis, str, len); } static Mode vis_modes[] = { @@ -588,8 +588,8 @@ XXX: CONTROL(' ') = 0, ^Space Go forward one word */ static KeyBinding nano_keys[] = { - { { CONTROL('D') }, call, { .f = vis_delete_key } }, - BACKSPACE( call, f, vis_backspace_key ), + { { CONTROL('D') }, call, { .f = editor_delete_key } }, + BACKSPACE( call, f, editor_backspace_key ), { { CONTROL('F') }, movement, { .i = MOVE_CHAR_NEXT } }, { { CONTROL('P') }, movement, { .i = MOVE_LINE_UP } }, { { CONTROL('N') }, movement, { .i = MOVE_LINE_DOWN } }, @@ -618,7 +618,7 @@ static KeyBinding nano_keys[] = { /* TODO: handle this in vis to insert \n\r when appriopriate */ { { CONTROL('M') }, insert, { .s = "\n" } }, #endif - { { CONTROL('L') }, call, { .f = vis_draw } }, + { { CONTROL('L') }, call, { .f = editor_draw } }, { /* empty last element, array terminator */ }, }; diff --git a/editor.c b/editor.c new file mode 100644 index 0000000..9af00d4 --- /dev/null +++ b/editor.c @@ -0,0 +1,483 @@ +#define _BSD_SOURCE +#include +#include +#include "editor.h" +#include "util.h" + +static EditorWin *editor_window_new_text(Editor *ed, Text *text); +static void editor_window_free(Editor *ed, EditorWin *win); +static void editor_window_split_internal(Editor *ed, const char *filename); +static void editor_windows_invalidate(Editor *ed, size_t start, size_t end); +static void editor_window_draw(EditorWin *win); +static void editor_windows_arrange_horizontal(Editor *ed); +static void editor_windows_arrange_vertical(Editor *ed); + +static Prompt *editor_prompt_new(); +static void editor_prompt_free(Prompt *prompt); +static void editor_prompt_clear(Prompt *prompt); +static void editor_prompt_resize(Prompt *prompt, int width, int height); +static void editor_prompt_move(Prompt *prompt, int x, int y); +static void editor_prompt_draw(Prompt *prompt); +static void editor_prompt_update(Prompt *prompt); + +static void editor_window_resize(EditorWin *win, int width, int height) { + window_resize(win->win, width, win->statuswin ? height - 1 : height); + if (win->statuswin) + wresize(win->statuswin, 1, width); + win->width = width; + win->height = height; +} + +static void editor_window_move(EditorWin *win, int x, int y) { + window_move(win->win, x, y); + if (win->statuswin) + mvwin(win->statuswin, y + win->height - 1, x); +} + +static void editor_window_statusbar_draw(EditorWin *win) { + size_t line, col; + if (win->statuswin && win->editor->statusbar) { + window_cursor_getxy(win->win, &line, &col); + win->editor->statusbar(win->statuswin, win->editor->win == win, + text_filename(win->text), line, col); + } +} + +static void editor_window_cursor_moved(Win *win, void *data) { + editor_window_statusbar_draw(data); +} + +void editor_statusbar_set(Editor *ed, editor_statusbar_t statusbar) { + ed->statusbar = statusbar; +} + +static void editor_windows_arrange_horizontal(Editor *ed) { + int n = 0, x = 0, y = 0; + for (EditorWin *win = ed->windows; win; win = win->next) + n++; + int height = ed->height / n; + for (EditorWin *win = ed->windows; win; win = win->next) { + editor_window_resize(win, ed->width, win->next ? height : ed->height - y); + editor_window_move(win, x, y); + y += height; + } +} + +static void editor_windows_arrange_vertical(Editor *ed) { + int n = 0, x = 0, y = 0; + for (EditorWin *win = ed->windows; win; win = win->next) + n++; + int width = ed->width / n - 1; + for (EditorWin *win = ed->windows; win; win = win->next) { + editor_window_resize(win, win->next ? width : ed->width - x, ed->height); + editor_window_move(win, x, y); + x += width; + if (win->next) + mvvline(0, x++, ACS_VLINE, ed->height); + } +} + +static void editor_window_split_internal(Editor *ed, const char *filename) { + EditorWin *sel = ed->win; + if (filename) { + // TODO? move this to editor_window_new + sel = NULL; + for (EditorWin *w = ed->windows; w; w = w->next) { + const char *f = text_filename(w->text); + if (f && strcmp(f, filename) == 0) { + sel = w; + break; + } + } + } + if (sel) { + EditorWin *win = editor_window_new_text(ed, sel->text); + win->text = sel->text; + window_syntax_set(win->win, window_syntax_get(sel->win)); + window_cursor_to(win->win, window_cursor_get(sel->win)); + } else { + editor_window_new(ed, filename); + } +} + +void editor_window_split(Editor *ed, const char *filename) { + editor_window_split_internal(ed, filename); + ed->windows_arrange = editor_windows_arrange_horizontal; + editor_draw(ed); +} + +void editor_window_vsplit(Editor *ed, const char *filename) { + editor_window_split_internal(ed, filename); + ed->windows_arrange = editor_windows_arrange_vertical; + editor_draw(ed); +} + +void editor_resize(Editor *ed, int width, int height) { + ed->width = width; + ed->height = height; + if (ed->prompt->active) { + ed->height--; + editor_prompt_resize(ed->prompt, ed->width, 1); + editor_prompt_move(ed->prompt, 0, ed->height); + editor_prompt_draw(ed->prompt); + } + editor_draw(ed); +} + +void editor_window_next(Editor *ed) { + EditorWin *sel = ed->win; + if (!sel) + return; + ed->win = ed->win->next; + if (!ed->win) + ed->win = ed->windows; + editor_window_statusbar_draw(sel); + editor_window_statusbar_draw(ed->win); +} + +void editor_window_prev(Editor *ed) { + EditorWin *sel = ed->win; + if (!sel) + return; + ed->win = ed->win->prev; + if (!ed->win) + for (ed->win = ed->windows; ed->win->next; ed->win = ed->win->next); + editor_window_statusbar_draw(sel); + editor_window_statusbar_draw(ed->win); +} + +static void editor_windows_invalidate(Editor *ed, size_t start, size_t end) { + for (EditorWin *win = ed->windows; win; win = win->next) { + if (ed->win != win && ed->win->text == win->text) { + Filerange view = window_viewport_get(win->win); + if ((view.start <= start && start <= view.end) || + (view.start <= end && end <= view.end)) + editor_window_draw(win); + } + } + editor_window_draw(ed->win); +} + + +bool editor_syntax_load(Editor *ed, Syntax *syntaxes, Color *colors) { + bool success = true; + ed->syntaxes = syntaxes; + for (Syntax *syn = syntaxes; syn && syn->name; syn++) { + if (regcomp(&syn->file_regex, syn->file, REG_EXTENDED|REG_NOSUB|REG_ICASE|REG_NEWLINE)) + success = false; + Color *color = colors; + for (int j = 0; j < LENGTH(syn->rules); j++) { + SyntaxRule *rule = &syn->rules[j]; + if (!rule->rule) + break; + if (rule->color.fg == 0 && color && color->fg != 0) + rule->color = *color++; + if (rule->color.attr == 0) + rule->color.attr = A_NORMAL; + if (rule->color.fg != 0) + rule->color.attr |= COLOR_PAIR(editor_color_reserve(rule->color.fg, rule->color.bg)); + if (regcomp(&rule->regex, rule->rule, REG_EXTENDED|rule->cflags)) + success = false; + } + } + + return success; +} + +void editor_syntax_unload(Editor *ed) { + for (Syntax *syn = ed->syntaxes; syn && syn->name; syn++) { + regfree(&syn->file_regex); + for (int j = 0; j < LENGTH(syn->rules); j++) { + SyntaxRule *rule = &syn->rules[j]; + if (!rule->rule) + break; + regfree(&rule->regex); + } + } + + ed->syntaxes = NULL; +} + +static void editor_window_draw(EditorWin *win) { + // TODO window_draw draw should restore cursor position + window_draw(win->win); + window_cursor_to(win->win, window_cursor_get(win->win)); +} + +void editor_draw(Editor *ed) { + erase(); + ed->windows_arrange(ed); + for (EditorWin *win = ed->windows; win; win = win->next) { + if (ed->win != win) + editor_window_draw(win); + } + editor_window_draw(ed->win); + wnoutrefresh(stdscr); +} + +void editor_update(Editor *ed) { + for (EditorWin *win = ed->windows; win; win = win->next) { + if (ed->win != win) { + if (win->statuswin) + wnoutrefresh(win->statuswin); + window_update(win->win); + } + } + + if (ed->win->statuswin) + wnoutrefresh(ed->win->statuswin); + if (ed->prompt && ed->prompt->active) + editor_prompt_update(ed->prompt); + window_update(ed->win->win); +} + +static void editor_window_free(Editor *ed, EditorWin *win) { + if (!win) + return; + window_free(win->win); + if (win->statuswin) + delwin(win->statuswin); + bool needed = false; + for (EditorWin *w = ed ? ed->windows : NULL; w; w = w->next) { + if (w->text == win->text) { + needed = true; + break; + } + } + if (!needed) + text_free(win->text); + free(win); +} + +static EditorWin *editor_window_new_text(Editor *ed, Text *text) { + EditorWin *win = calloc(1, sizeof(EditorWin)); + if (!win) + return NULL; + win->editor = ed; + win->text = text; + win->win = window_new(win->text); + win->statuswin = newwin(1, ed->width, 0, 0); + if (!win->win || !win->statuswin) { + editor_window_free(ed, win); + return NULL; + } + window_cursor_watch(win->win, editor_window_cursor_moved, win); + if (ed->windows) + ed->windows->prev = win; + win->next = ed->windows; + win->prev = NULL; + ed->windows = win; + ed->win = win; + return win; +} + +bool editor_window_new(Editor *ed, const char *filename) { + Text *text = text_load(filename); + if (!text) + return false; + + EditorWin *win = editor_window_new_text(ed, text); + if (!win) { + text_free(text); + return false; + } + + if (filename) { + for (Syntax *syn = ed->syntaxes; syn && syn->name; syn++) { + if (!regexec(&syn->file_regex, filename, 0, NULL, 0)) { + window_syntax_set(win->win, syn); + break; + } + } + } + + editor_draw(ed); + + return true; +} + +static void editor_window_detach(Editor *ed, EditorWin *win) { + if (win->prev) + win->prev->next = win->next; + if (win->next) + win->next->prev = win->prev; + if (ed->windows == win) + ed->windows = win->next; + win->next = win->prev = NULL; +} + +void editor_window_close(Editor *ed) { + EditorWin *win = ed->win; + ed->win = win->next ? win->next : win->prev; + editor_window_detach(ed, win); + editor_window_free(ed, win); +} + +Editor *editor_new(int width, int height) { + Editor *ed = calloc(1, sizeof(Editor)); + if (!ed) + return NULL; + if (!(ed->prompt = editor_prompt_new())) + goto err; + if (!(ed->search_pattern = text_regex_new())) + goto err; + ed->width = width; + ed->height = height; + ed->windows_arrange = editor_windows_arrange_horizontal; + ed->running = true; + return ed; +err: + editor_free(ed); + return NULL; +} + +void editor_free(Editor *ed) { + while (ed->windows) + editor_window_close(ed); + editor_prompt_free(ed->prompt); + text_regex_free(ed->search_pattern); + for (int i = 0; i < REG_LAST; i++) + register_free(&ed->registers[i]); + editor_syntax_unload(ed); + free(ed); +} + +void editor_insert_key(Editor *ed, const char *c, size_t len) { + Win *win = ed->win->win; + size_t start = window_cursor_get(win); + window_insert_key(win, c, len); + editor_windows_invalidate(ed, start, start + len); +} + +void editor_replace_key(Editor *ed, const char *c, size_t len) { + Win *win = ed->win->win; + size_t start = window_cursor_get(win); + window_replace_key(win, c, len); + editor_windows_invalidate(ed, start, start + 6); +} + +void editor_backspace_key(Editor *ed) { + Win *win = ed->win->win; + size_t end = window_cursor_get(win); + size_t start = window_backspace_key(win); + editor_windows_invalidate(ed, start, end); +} + +void editor_delete_key(Editor *ed) { + size_t start = window_delete_key(ed->win->win); + editor_windows_invalidate(ed, start, start + 6); +} + +void editor_insert(Editor *ed, size_t pos, const char *c, size_t len) { + text_insert_raw(ed->win->text, pos, c, len); + editor_windows_invalidate(ed, pos, pos + len); +} + +void editor_delete(Editor *ed, size_t pos, size_t len) { + text_delete(ed->win->text, pos, len); + editor_windows_invalidate(ed, pos, pos + len); +} + + +static void editor_prompt_free(Prompt *prompt) { + if (!prompt) + return; + editor_window_free(NULL, prompt->win); + if (prompt->titlewin) + delwin(prompt->titlewin); + free(prompt->title); + free(prompt); +} + +static Prompt *editor_prompt_new() { + Text *text = text_load(NULL); + if (!text) + return NULL; + Prompt *prompt = calloc(1, sizeof(Prompt)); + if (!prompt) + goto err; + + if (!(prompt->win = calloc(1, sizeof(EditorWin)))) + goto err; + + if (!(prompt->win->win = window_new(text))) + goto err; + + prompt->win->text = text; + + if (!(prompt->titlewin = newwin(0, 0, 0, 0))) + goto err; + + return prompt; +err: + if (!prompt || !prompt->win) + text_free(text); + editor_prompt_free(prompt); + return NULL; +} + +static void editor_prompt_resize(Prompt *prompt, int width, int height) { + size_t title_width = strlen(prompt->title); + wresize(prompt->titlewin, height, title_width); + editor_window_resize(prompt->win, width - title_width, height); +} + +static void editor_prompt_move(Prompt *prompt, int x, int y) { + size_t title_width = strlen(prompt->title); + mvwin(prompt->titlewin, y, x); + editor_window_move(prompt->win, x + title_width, y); +} + +void editor_prompt_show(Editor *ed, const char *title) { + Prompt *prompt = ed->prompt; + if (prompt->active) + return; + prompt->active = true; + prompt->editor = ed->win; + ed->win = prompt->win; + free(prompt->title); + prompt->title = strdup(title); + editor_resize(ed, ed->width, ed->height); +} + +static void editor_prompt_draw(Prompt *prompt) { + mvwaddstr(prompt->titlewin, 0, 0, prompt->title); +} + +static void editor_prompt_update(Prompt *prompt) { + wnoutrefresh(prompt->titlewin); +} + +static void editor_prompt_clear(Prompt *prompt) { + Text *text = prompt->win->text; + while (text_undo(text)); +} + +void editor_prompt_hide(Editor *ed) { + Prompt *prompt = ed->prompt; + if (!prompt->active) + return; + prompt->active = false; + ed->win = prompt->editor; + prompt->editor = NULL; + ed->height++; + editor_prompt_clear(prompt); + editor_draw(ed); +} + +void editor_prompt_set(Editor *ed, const char *line) { + Text *text = ed->prompt->win->text; + editor_prompt_clear(ed->prompt); + text_insert_raw(text, 0, line, strlen(line)); + editor_window_draw(ed->prompt->win); +} + +char *editor_prompt_get(Editor *ed) { + Text *text = ed->prompt->win->text; + char *buf = malloc(text_size(text) + 1); + if (!buf) + return NULL; + size_t len = text_bytes_get(text, 0, text_size(text), buf); + buf[len] = '\0'; + return buf; +} diff --git a/editor.h b/editor.h new file mode 100644 index 0000000..57794e2 --- /dev/null +++ b/editor.h @@ -0,0 +1,166 @@ +#ifndef EDITOR_H +#define EDITOR_H + +#include +#include +#include "text-motions.h" +#include "text-objects.h" +#include "window.h" +#include "register.h" + +typedef struct Editor Editor; +typedef struct EditorWin EditorWin; + +struct EditorWin { + Editor *editor; /* editor instance to which this window belongs */ + Text *text; /* underlying text management */ + Win *win; /* vis window for the text area */ + WINDOW *statuswin; /* curses window for the statusbar */ + int width, height; /* window size including the statusbar */ + EditorWin *prev, *next; /* neighbouring windows */ +}; + +typedef struct { + EditorWin *win; /* 1-line height editor window used for the prompt */ + EditorWin *editor; /* active editor window before prompt is shown */ + char *title; /* title displayed to the left of the prompt */ + WINDOW *titlewin; /* the curses window holding the prompt title */ + bool active; /* whether the prompt is currently shown or not */ +} Prompt; + +typedef void (*editor_statusbar_t)(WINDOW *win, bool active, const char *filename, size_t line, size_t col); + +enum Reg { + REG_a, + REG_b, + REG_c, + REG_d, + REG_e, + REG_f, + REG_g, + REG_h, + REG_i, + REG_j, + REG_k, + REG_l, + REG_m, + REG_n, + REG_o, + REG_p, + REG_q, + REG_r, + REG_s, + REG_t, + REG_u, + REG_v, + REG_w, + REG_x, + REG_y, + REG_z, + REG_DEFAULT, + REG_LAST, +}; + +enum Mark { + MARK_a, + MARK_b, + MARK_c, + MARK_d, + MARK_e, + MARK_f, + MARK_g, + MARK_h, + MARK_i, + MARK_j, + MARK_k, + MARK_l, + MARK_m, + MARK_n, + MARK_o, + MARK_p, + MARK_q, + MARK_r, + MARK_s, + MARK_t, + MARK_u, + MARK_v, + MARK_w, + MARK_x, + MARK_y, + MARK_z, +}; + +struct Editor { + int width, height; /* terminal size, available for all windows */ + EditorWin *windows; /* list of windows */ + EditorWin *win; /* currently active window */ + Syntax *syntaxes; /* NULL terminated array of syntax definitions */ + Register registers[REG_LAST]; /* register used for copy and paste */ + Prompt *prompt; /* used to get user input */ + Regex *search_pattern; /* last used search pattern */ + void (*windows_arrange)(Editor*); /* current layout which places the windows */ + editor_statusbar_t statusbar; /* configurable user hook to draw statusbar */ + bool running; /* (TODO move elsewhere?) */ +}; + + +typedef struct { + short fg, bg; /* fore and background color */ + int attr; /* curses attributes */ +} Color; + +typedef struct { + char *rule; /* regex to search for */ + int cflags; /* compilation flags (REG_*) used when compiling */ + Color color; /* settings to apply in case of a match */ + regex_t regex; /* compiled form of the above rule */ +} SyntaxRule; + +#define SYNTAX_REGEX_RULES 10 + +typedef struct Syntax Syntax; + +struct Syntax { /* a syntax definition */ + char *name; /* syntax name */ + char *file; /* apply to files matching this regex */ + regex_t file_regex; /* compiled file name regex */ + SyntaxRule rules[SYNTAX_REGEX_RULES]; /* all rules for this file type */ +}; + +Editor *editor_new(int width, int height); +void editor_free(Editor*); +void editor_resize(Editor*, int width, int height); +void editor_draw(Editor*); +void editor_update(Editor*); +void editor_insert_key(Editor*, const char *c, size_t len); +void editor_replace_key(Editor*, const char *c, size_t len); +void editor_backspace_key(Editor*); +void editor_delete_key(Editor*); +void editor_insert(Editor*, size_t pos, const char *data, size_t len); +void editor_delete(Editor*, size_t pos, size_t len); + +// TODO comment +bool editor_syntax_load(Editor*, Syntax *syntaxes, Color *colors); +void editor_syntax_unload(Editor*); + +bool editor_window_new(Editor*, const char *filename); +void editor_window_close(Editor *vis); +void editor_window_split(Editor*, const char *filename); +void editor_window_vsplit(Editor*, const char *filename); +void editor_window_next(Editor*); +void editor_window_prev(Editor*); + +char *editor_prompt_get(Editor *vis); +void editor_prompt_set(Editor *vis, const char *line); +void editor_prompt_show(Editor *vis, const char *title); +void editor_prompt_hide(Editor *vis); + + +void editor_statusbar_set(Editor*, editor_statusbar_t); + +/* library initialization code, should be run at startup */ +void editor_init(void); +short editor_color_reserve(short fg, short bg); +short editor_color_get(short fg, short bg); + +#endif diff --git a/main.c b/main.c deleted file mode 100644 index 2a4e08d..0000000 --- a/main.c +++ /dev/null @@ -1,1002 +0,0 @@ -#define _POSIX_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "vis.h" -#include "util.h" - -#ifdef PDCURSES -int ESCDELAY; -#endif -#ifndef NCURSES_REENTRANT -# define set_escdelay(d) (ESCDELAY = (d)) -#endif - -typedef union { - bool b; - size_t i; - const char *s; - size_t (*m)(Win*); - void (*f)(Vis*); -} 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; - bool common_prefix; - void (*enter)(Mode *old); - void (*leave)(Mode *new); - bool (*unknown)(Key *key0, Key *key1); /* unknown key for this mode, return value determines whether parent modes will be checked */ - void (*input)(const char *str, size_t len); /* unknown key for this an all parent modes */ - void (*idle)(void); -}; - -typedef struct { - char *name; - Mode *mode; - vis_statusbar_t statusbar; -} Config; - -typedef struct { - int count; - Register *reg; - Filerange range; - size_t pos; - bool linewise; -} OperatorContext; - -typedef struct { - void (*func)(OperatorContext*); /* function implementing the operator logic */ - bool selfcontained; /* is this operator followed by movements/text-objects */ -} Operator; - -typedef struct { - size_t (*cmd)(const Arg*); - size_t (*win)(Win*); - size_t (*txt)(Text*, size_t pos); - enum { - LINEWISE = 1 << 0, - CHARWISE = 1 << 1, - INCLUSIVE = 1 << 2, - EXCLUSIVE = 1 << 3, - } type; - int count; -} Movement; - -typedef struct { - Filerange (*range)(Text*, size_t pos); - enum { - INNER, - OUTER, - } type; -} TextObject; - -typedef struct { - int count; - bool linewise; - Operator *op; - Movement *movement; - TextObject *textobj; - Register *reg; - Mark mark; - Key key; - Arg arg; -} Action; - -typedef struct { - const char *name; - bool (*cmd)(const char *argv[]); - regex_t regex; -} Command; - -/* global variables */ -static Vis *vis; /* global editor instance, keeps track of all windows etc. */ -static Mode *mode; /* currently active mode, used to search for keybindings */ -static Mode *mode_prev; /* mode which was active previously */ -static Action action; /* current action which is in progress */ -static Action action_prev; /* last operator action used by the repeat '.' key */ - -/* movements which can be used besides the one in text-motions.h and window.h */ -static size_t search_forward(const Arg *arg); -static size_t search_backward(const Arg *arg); -static size_t mark_goto(const Arg *arg); -static size_t mark_line_goto(const Arg *arg); -static size_t to(const Arg *arg); -static size_t till(const Arg *arg); -static size_t to_left(const Arg *arg); -static size_t till_left(const Arg *arg); -static size_t line(const Arg *arg); -static size_t column(const Arg *arg); - -enum { - MOVE_LINE_UP, - MOVE_LINE_DOWN, - MOVE_LINE_BEGIN, - MOVE_LINE_START, - MOVE_LINE_FINISH, - MOVE_LINE_END, - MOVE_LINE, - MOVE_COLUMN, - MOVE_CHAR_PREV, - MOVE_CHAR_NEXT, - MOVE_WORD_START_PREV, - MOVE_WORD_START_NEXT, - MOVE_WORD_END_PREV, - MOVE_WORD_END_NEXT, - MOVE_SENTENCE_PREV, - MOVE_SENTENCE_NEXT, - MOVE_PARAGRAPH_PREV, - MOVE_PARAGRAPH_NEXT, - MOVE_BRACKET_MATCH, - MOVE_LEFT_TO, - MOVE_RIGHT_TO, - MOVE_LEFT_TILL, - MOVE_RIGHT_TILL, - MOVE_FILE_BEGIN, - MOVE_FILE_END, - MOVE_MARK, - MOVE_MARK_LINE, - MOVE_SEARCH_FORWARD, - MOVE_SEARCH_BACKWARD, -}; - -static Movement moves[] = { - [MOVE_LINE_UP] = { .win = window_line_up }, - [MOVE_LINE_DOWN] = { .win = window_line_down }, - [MOVE_LINE_BEGIN] = { .txt = text_line_begin, .type = LINEWISE }, - [MOVE_LINE_START] = { .txt = text_line_start, .type = LINEWISE }, - [MOVE_LINE_FINISH] = { .txt = text_line_finish, .type = LINEWISE }, - [MOVE_LINE_END] = { .txt = text_line_end, .type = LINEWISE }, - [MOVE_LINE] = { .cmd = line, .type = LINEWISE }, - [MOVE_COLUMN] = { .cmd = column, .type = CHARWISE }, - [MOVE_CHAR_PREV] = { .win = window_char_prev }, - [MOVE_CHAR_NEXT] = { .win = window_char_next }, - [MOVE_WORD_START_PREV] = { .txt = text_word_start_prev, .type = CHARWISE }, - [MOVE_WORD_START_NEXT] = { .txt = text_word_start_next, .type = CHARWISE }, - [MOVE_WORD_END_PREV] = { .txt = text_word_end_prev, .type = CHARWISE|INCLUSIVE }, - [MOVE_WORD_END_NEXT] = { .txt = text_word_end_next, .type = CHARWISE|INCLUSIVE }, - [MOVE_SENTENCE_PREV] = { .txt = text_sentence_prev, .type = LINEWISE }, - [MOVE_SENTENCE_NEXT] = { .txt = text_sentence_next, .type = LINEWISE }, - [MOVE_PARAGRAPH_PREV] = { .txt = text_paragraph_prev, .type = LINEWISE }, - [MOVE_PARAGRAPH_NEXT] = { .txt = text_paragraph_next, .type = LINEWISE }, - [MOVE_BRACKET_MATCH] = { .txt = text_bracket_match, .type = LINEWISE|INCLUSIVE }, - [MOVE_FILE_BEGIN] = { .txt = text_begin, .type = LINEWISE }, - [MOVE_FILE_END] = { .txt = text_end, .type = LINEWISE }, - [MOVE_LEFT_TO] = { .cmd = to_left, .type = LINEWISE }, - [MOVE_RIGHT_TO] = { .cmd = to, .type = LINEWISE }, - [MOVE_LEFT_TILL] = { .cmd = till_left, .type = LINEWISE }, - [MOVE_RIGHT_TILL] = { .cmd = till, .type = LINEWISE }, - [MOVE_MARK] = { .cmd = mark_goto, .type = LINEWISE }, - [MOVE_MARK_LINE] = { .cmd = mark_line_goto, .type = LINEWISE }, - [MOVE_SEARCH_FORWARD] = { .cmd = search_forward, .type = LINEWISE }, - [MOVE_SEARCH_BACKWARD] = { .cmd = search_backward, .type = LINEWISE }, -}; - -/* available text objects */ -enum { - TEXT_OBJ_WORD, - TEXT_OBJ_LINE_UP, - TEXT_OBJ_LINE_DOWN, - TEXT_OBJ_SENTENCE, - TEXT_OBJ_PARAGRAPH, - TEXT_OBJ_OUTER_SQUARE_BRACKET, - TEXT_OBJ_INNER_SQUARE_BRACKET, - TEXT_OBJ_OUTER_CURLY_BRACKET, - TEXT_OBJ_INNER_CURLY_BRACKET, - TEXT_OBJ_OUTER_ANGLE_BRACKET, - TEXT_OBJ_INNER_ANGLE_BRACKET, - TEXT_OBJ_OUTER_PARANTHESE, - TEXT_OBJ_INNER_PARANTHESE, - TEXT_OBJ_OUTER_QUOTE, - TEXT_OBJ_INNER_QUOTE, - TEXT_OBJ_OUTER_SINGLE_QUOTE, - TEXT_OBJ_INNER_SINGLE_QUOTE, - TEXT_OBJ_OUTER_BACKTICK, - TEXT_OBJ_INNER_BACKTICK, -}; - -static TextObject textobjs[] = { - [TEXT_OBJ_WORD] = { text_object_word }, - [TEXT_OBJ_LINE_UP] = { text_object_line }, - [TEXT_OBJ_LINE_DOWN] = { text_object_line }, - [TEXT_OBJ_SENTENCE] = { text_object_sentence }, - [TEXT_OBJ_PARAGRAPH] = { text_object_paragraph }, - [TEXT_OBJ_OUTER_SQUARE_BRACKET] = { text_object_square_bracket, OUTER }, - [TEXT_OBJ_INNER_SQUARE_BRACKET] = { text_object_square_bracket, INNER }, - [TEXT_OBJ_OUTER_CURLY_BRACKET] = { text_object_curly_bracket, OUTER }, - [TEXT_OBJ_INNER_CURLY_BRACKET] = { text_object_curly_bracket, INNER }, - [TEXT_OBJ_OUTER_ANGLE_BRACKET] = { text_object_angle_bracket, OUTER }, - [TEXT_OBJ_INNER_ANGLE_BRACKET] = { text_object_angle_bracket, INNER }, - [TEXT_OBJ_OUTER_PARANTHESE] = { text_object_paranthese, OUTER }, - [TEXT_OBJ_INNER_PARANTHESE] = { text_object_paranthese, INNER }, - [TEXT_OBJ_OUTER_QUOTE] = { text_object_quote, OUTER }, - [TEXT_OBJ_INNER_QUOTE] = { text_object_quote, INNER }, - [TEXT_OBJ_OUTER_SINGLE_QUOTE] = { text_object_single_quote, OUTER }, - [TEXT_OBJ_INNER_SINGLE_QUOTE] = { text_object_single_quote, INNER }, - [TEXT_OBJ_OUTER_BACKTICK] = { text_object_backtick, OUTER }, - [TEXT_OBJ_INNER_BACKTICK] = { text_object_backtick, INNER }, -}; - -/* if some movements are forced to be linewise, they are translated to text objects */ -static TextObject *moves_linewise[] = { - [MOVE_LINE_UP] = &textobjs[TEXT_OBJ_LINE_UP], - [MOVE_LINE_DOWN] = &textobjs[TEXT_OBJ_LINE_DOWN], -}; - -/* functions to be called from keybindings */ -static void switchmode(const Arg *arg); -static void mark_set(const Arg *arg); -static void insert(const Arg *arg); -static void insert_tab(const Arg *arg); -static void insert_newline(const Arg *arg); -static void split(const Arg *arg); -static void quit(const Arg *arg); -static void repeat(const Arg *arg); -static void count(const Arg *arg); -static void linewise(const Arg *arg); -static void operator(const Arg *arg); -static void movement_key(const Arg *arg); -static void movement(const Arg *arg); -static void textobj(const Arg *arg); -static void reg(const Arg *arg); -static void mark(const Arg *arg); -static void mark_line(const Arg *arg); -static void undo(const Arg *arg); -static void redo(const Arg *arg); -static void zero(const Arg *arg); -static void delete_word(const Arg *arg); -static void insert_register(const Arg *arg); -static void prompt(const Arg *arg); -static void prompt_enter(const Arg *arg); -static void prompt_up(const Arg *arg); -static void prompt_down(const Arg *arg); -static void insert_verbatim(const Arg *arg); -static void cursor(const Arg *arg); -static void call(const Arg *arg); - -/* commands to enter at the ':'-prompt */ -static bool cmd_gotoline(const char *argv[]); -static bool cmd_open(const char *argv[]); -static bool cmd_quit(const char *argv[]); -static bool cmd_read(const char *argv[]); -static bool cmd_substitute(const char *argv[]); -static bool cmd_split(const char *argv[]); -static bool cmd_vsplit(const char *argv[]); -static bool cmd_wq(const char *argv[]); -static bool cmd_write(const char *argv[]); - -static void action_reset(Action *a); -static void switchmode_to(Mode *new_mode); - -#include "config.h" - -static Key getkey(void); -static void action_do(Action *a); -static bool exec_command(char *cmdline); - -/* operator implementations of type: void (*op)(OperatorContext*) */ - -static void op_delete(OperatorContext *c) { - size_t len = c->range.end - c->range.start; - c->reg->linewise = c->linewise; - register_put(c->reg, vis->win->text, &c->range); - vis_delete(vis, c->range.start, len); - window_cursor_to(vis->win->win, c->range.start); -} - -static void op_change(OperatorContext *c) { - op_delete(c); - switchmode(&(const Arg){ .i = VIS_MODE_INSERT }); -} - -static void op_yank(OperatorContext *c) { - c->reg->linewise = c->linewise; - register_put(c->reg, vis->win->text, &c->range); -} - -static void op_paste(OperatorContext *c) { - size_t pos = window_cursor_get(vis->win->win); - if (c->reg->linewise) - pos = text_line_next(vis->win->text, pos); - vis_insert(vis, pos, c->reg->data, c->reg->len); - window_cursor_to(vis->win->win, pos + c->reg->len); -} - -/* movement implementations of type: size_t (*move)(const Arg*) */ - -static size_t search_forward(const Arg *arg) { - size_t pos = window_cursor_get(vis->win->win); - return text_search_forward(vis->win->text, pos, vis->search_pattern); -} - -static size_t search_backward(const Arg *arg) { - size_t pos = window_cursor_get(vis->win->win); - return text_search_backward(vis->win->text, pos, vis->search_pattern); -} - -static void mark_set(const Arg *arg) { - text_mark_set(vis->win->text, arg->i, window_cursor_get(vis->win->win)); -} - -static size_t mark_goto(const Arg *arg) { - return text_mark_get(vis->win->text, action.mark); -} - -static size_t mark_line_goto(const Arg *arg) { - return text_line_start(vis->win->text, mark_goto(arg)); -} - -static size_t to(const Arg *arg) { - return text_find_char_next(vis->win->text, window_cursor_get(vis->win->win) + 1, - action.key.str, strlen(action.key.str)); -} - -static size_t till(const Arg *arg) { - return text_char_prev(vis->win->text, to(arg)); -} - -static size_t to_left(const Arg *arg) { - return text_find_char_prev(vis->win->text, window_cursor_get(vis->win->win) - 1, - action.key.str, strlen(action.key.str)); -} - -static size_t till_left(const Arg *arg) { - return text_char_next(vis->win->text, to_left(arg)); -} - -static size_t line(const Arg *arg) { - if (action.count == 0) - return text_size(vis->win->text); - size_t pos = text_pos_by_lineno(vis->win->text, action.count); - action.count = 0; - return pos; -} - -static size_t column(const Arg *arg) { - char c; - VisWin *win = vis->win; - size_t pos = window_cursor_get(win->win); - Iterator it = text_iterator_get(win->text, text_line_begin(win->text, pos)); - while (action.count-- > 0 && text_iterator_byte_get(&it, &c) && c != '\n') - text_iterator_byte_next(&it, NULL); - action.count = 0; - return it.pos; -} - -/* key bindings functions of type: void (*func)(const Arg*) */ - -static void repeat(const Arg *arg) { - action = action_prev; - action_do(&action); -} - -static void count(const Arg *arg) { - action.count = action.count * 10 + arg->i; -} - -static void linewise(const Arg *arg) { - action.linewise = arg->b; -} - -static void operator(const Arg *arg) { - Operator *op = &ops[arg->i]; - if (mode == &vis_modes[VIS_MODE_VISUAL]) { - action.op = op; - action_do(&action); - return; - } - - switchmode(&(const Arg){ .i = VIS_MODE_OPERATOR }); - if (action.op == op) { - /* hacky way to handle double operators i.e. things like - * dd, yy etc where the second char isn't a movement */ - action.linewise = true; - action.textobj = moves_linewise[MOVE_LINE_DOWN]; - action_do(&action); - } else { - action.op = op; - if (op->selfcontained) - action_do(&action); - } -} - -static void movement_key(const Arg *arg) { - Key k = getkey(); - if (!k.str[0]) { - action_reset(&action); - return; - } - action.key = k; - action.movement = &moves[arg->i]; - action_do(&action); -} - -static void movement(const Arg *arg) { - if (action.linewise && arg->i < LENGTH(moves_linewise)) - action.textobj = moves_linewise[arg->i]; - else - action.movement = &moves[arg->i]; - action_do(&action); -} - -static void textobj(const Arg *arg) { - action.textobj = &textobjs[arg->i]; - action_do(&action); -} - -static void reg(const Arg *arg) { - action.reg = &vis->registers[arg->i]; -} - -static void mark(const Arg *arg) { - action.mark = arg->i; - action.movement = &moves[MOVE_MARK]; - action_do(&action); -} - -static void mark_line(const Arg *arg) { - action.mark = arg->i; - action.movement = &moves[MOVE_MARK_LINE]; - action_do(&action); -} - -static void undo(const Arg *arg) { - if (text_undo(vis->win->text)) - vis_draw(vis); -} - -static void redo(const Arg *arg) { - if (text_redo(vis->win->text)) - vis_draw(vis); -} - -static void zero(const Arg *arg) { - if (action.count == 0) - movement(&(const Arg){ .i = MOVE_LINE_BEGIN }); - else - count(&(const Arg){ .i = 0 }); -} - -static void delete_word(const Arg *arg) { - operator(&(const Arg){ .i = OP_DELETE }); - movement(&(const Arg){ .i = MOVE_WORD_START_PREV }); -} - -static void insert_register(const Arg *arg) { - Register *reg = &vis->registers[arg->i]; - vis_insert(vis, window_cursor_get(vis->win->win), reg->data, reg->len); -} - -static void prompt(const Arg *arg) { - vis_prompt_show(vis, arg->s); - switchmode(&(const Arg){ .i = VIS_MODE_PROMPT }); -} - -static void prompt_enter(const Arg *arg) { - char *s = vis_prompt_get(vis); - switchmode(&(const Arg){ .i = VIS_MODE_NORMAL }); - switch (vis->prompt->title[0]) { - case '/': - case '?': - if (text_regex_compile(vis->search_pattern, s, REG_EXTENDED)) { - action_reset(&action); - } else { - movement(&(const Arg){ .i = vis->prompt->title[0] == '/' ? - MOVE_SEARCH_FORWARD : MOVE_SEARCH_BACKWARD }); - } - break; - case ':': - exec_command(s); - break; - } - free(s); -} - -static void prompt_up(const Arg *arg) { - -} - -static void prompt_down(const Arg *arg) { - -} - -static void insert_verbatim(const Arg *arg) { - int value = 0; - for (int i = 0; i < 3; i++) { - Key k = getkey(); - if (k.str[0] < '0' || k.str[0] > '9') - return; - value = value * 10 + k.str[0] - '0'; - } - char v = value; - vis_insert(vis, window_cursor_get(vis->win->win), &v, 1); -} - -static void quit(const Arg *arg) { - vis->running = false; -} - -static void split(const Arg *arg) { - vis_window_split(vis, arg->s); -} - -static void cursor(const Arg *arg) { - arg->m(vis->win->win); -} - -static void call(const Arg *arg) { - arg->f(vis); -} - -static void insert(const Arg *arg) { - vis_insert_key(vis, arg->s, arg->s ? strlen(arg->s) : 0); -} - -static void insert_tab(const Arg *arg) { - insert(&(const Arg){ .s = "\t" }); -} - -static void insert_newline(const Arg *arg) { - // TODO determine file type to insert \n\r or \n - insert(&(const Arg){ .s = "\n" }); -} - -static void switchmode(const Arg *arg) { - switchmode_to(&vis_modes[arg->i]); -} - -/* action processing, executed the operator / movement / text object */ - -static void action_do(Action *a) { - Text *txt = vis->win->text; - Win *win = vis->win->win; - size_t pos = window_cursor_get(win); - if (a->count == 0) - a->count = 1; - OperatorContext c = { - .count = a->count, - .pos = pos, - .reg = a->reg ? a->reg : &vis->registers[REG_DEFAULT], - .linewise = a->linewise, - }; - - if (a->movement) { - size_t start = pos; - for (int i = 0; i < a->count; i++) { - if (a->movement->txt) - pos = a->movement->txt(txt, pos); - else if (a->movement->win) - pos = a->movement->win(win); - else - pos = a->movement->cmd(&a->arg); - if (pos == (size_t)-1) - break; - } - - if (pos == (size_t)-1) { - c.range.start = start; - c.range.end = start; - } else { - c.range.start = MIN(start, pos); - c.range.end = MAX(start, pos); - } - - if (!a->op) { - if (a->movement->type & CHARWISE) - window_scroll_to(win, pos); - else - window_cursor_to(win, pos); - } else if (a->movement->type & INCLUSIVE) { - Iterator it = text_iterator_get(txt, c.range.end); - text_iterator_char_next(&it, NULL); - c.range.end = it.pos; - } - } else if (a->textobj) { - Filerange r; - c.range.start = c.range.end = pos; - for (int i = 0; i < a->count; i++) { - r = a->textobj->range(txt, pos); - // TODO range_valid? - if (r.start == (size_t)-1 || r.end == (size_t)-1) - break; - if (a->textobj->type == OUTER) { - r.start--; - r.end++; - } - // TODO c.range = range_union(&c.range, &r); - c.range.start = MIN(c.range.start, r.start); - c.range.end = MAX(c.range.end, r.end); - if (i < a->count - 1) { - if (a->textobj == &textobjs[TEXT_OBJ_LINE_UP]) { - pos = c.range.start - 1; - } else { - pos = c.range.end + 1; - } - } - } - } else if (mode == &vis_modes[VIS_MODE_VISUAL]) { - c.range = window_selection_get(win); - if (c.range.start == (size_t)-1 || c.range.end == (size_t)-1) - c.range.start = c.range.end = pos; - } - - if (a->op) { - a->op->func(&c); - if (mode == &vis_modes[VIS_MODE_OPERATOR]) - switchmode_to(mode_prev); - else if (mode == &vis_modes[VIS_MODE_VISUAL]) - switchmode(&(const Arg){ .i = VIS_MODE_NORMAL }); - text_snapshot(txt); - } - - if (a != &action_prev) { - if (a->op) - action_prev = *a; - action_reset(a); - } -} - -static void action_reset(Action *a) { - a->count = 0; - a->linewise = false; - a->op = NULL; - a->movement = NULL; - a->textobj = NULL; - a->reg = NULL; -} - -static void switchmode_to(Mode *new_mode) { - if (mode == new_mode) - return; - if (mode->leave) - mode->leave(new_mode); - mode_prev = mode; - //fprintf(stderr, "%s -> %s\n", mode_prev->name, new_mode->name); - mode = new_mode; - if (mode->enter) - mode->enter(mode_prev); - // TODO display mode name somewhere? - -} - - - -/* ':'-command implementations */ - -static bool cmd_gotoline(const char *argv[]) { - action.count = strtoul(argv[0], NULL, 10); - movement(&(const Arg){ .i = MOVE_LINE }); - return true; -} - -static bool cmd_open(const char *argv[]) { - for (const char **file = &argv[1]; *file; file++) - vis_window_new(vis, *file); - return true; -} - -static bool cmd_quit(const char *argv[]) { - bool force = strchr(argv[0], '!') != NULL; - for (VisWin *win = vis->windows; win; win = win->next) { - if (text_modified(win->text) && !force) - return false; - } - vis->running = false; - return true; -} - -static bool cmd_read(const char *argv[]) { - size_t pos = window_cursor_get(vis->win->win); - for (const char **file = &argv[1]; *file; file++) { - int fd = open(*file, O_RDONLY); - char *data = NULL; - struct stat info; - if (fd == -1) - goto err; - if (fstat(fd, &info) == -1) - goto err; - if (!S_ISREG(info.st_mode)) - goto err; - // XXX: use lseek(fd, 0, SEEK_END); instead? - data = mmap(NULL, info.st_size, PROT_READ, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) - goto err; - - text_insert_raw(vis->win->text, pos, data, info.st_size); - pos += info.st_size; - err: - if (fd > 2) - close(fd); - if (data && data != MAP_FAILED) - munmap(data, info.st_size); - } - vis_draw(vis); - return true; -} - -static bool cmd_substitute(const char *argv[]) { - // TODO - return true; -} - -static bool cmd_split(const char *argv[]) { - vis_window_split(vis, argv[1]); - for (const char **file = &argv[2]; *file; file++) - vis_window_split(vis, *file); - return true; -} - -static bool cmd_vsplit(const char *argv[]) { - vis_window_vsplit(vis, argv[1]); - for (const char **file = &argv[2]; *file; file++) - vis_window_vsplit(vis, *file); - return true; -} - -static bool cmd_wq(const char *argv[]) { - if (cmd_write(argv)) - return cmd_quit(argv); - return false; -} - -static bool cmd_write(const char *argv[]) { - Text *text = vis->win->text; - if (!argv[1]) - argv[1] = text_filename(text); - for (const char **file = &argv[1]; *file; file++) { - if (text_save(text, *file)) - return false; - } - return true; -} - -static bool exec_command(char *cmdline) { - static bool init = false; - if (!init) { - /* compile the regexes on first inovaction */ - for (Command *c = cmds; c->name; c++) - regcomp(&c->regex, c->name, REG_EXTENDED); - init = true; - } - - Command *cmd = NULL; - for (Command *c = cmds; c->name; c++) { - if (!regexec(&c->regex, cmdline, 0, NULL, 0)) { - cmd = c; - break; - } - } - - if (!cmd) - return false; - - const char *argv[32] = { cmdline }; - char *s = cmdline; - for (int i = 1; i < LENGTH(argv); i++) { - if (s) { - if ((s = strchr(s, ' '))) - *s++ = '\0'; - } - while (s && *s && *s == ' ') - s++; - argv[i] = s ? s : NULL; - } - - cmd->cmd(argv); - return true; -} - -/* default editor configuration to use */ -static Config *config = &editors[0]; - - -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() { - endwin(); - //delscreen(set_term(NULL)); -} - -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) { - if (mode->common_prefix && !keymatch(key0, &mode->bindings->key[0])) - continue; - 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; - } - if (mode->unknown && !mode->unknown(key0, key1)) - break; - } - return NULL; -} - -static Key getkey(void) { - Key key = { .str = "\0\0\0\0\0\0", .code = 0 }; - int keycode = getch(), len = 0; - if (keycode == ERR) - return key; - - if (keycode >= KEY_MIN) { - key.code = keycode; - } else { - char keychar = keycode; - key.str[len++] = keychar; - - 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); - } - } - - return key; -} - -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_prev = mode = config->mode; - setup(); - - if (!(vis = vis_new(screen.w, screen.h))) - return 1; - if (!vis_syntax_load(vis, syntaxes, colors)) - return 1; - vis_statusbar_set(vis, config->statusbar); - - if (!vis_window_new(vis, argc > 1 ? argv[1] : NULL)) - return 1; - for (int i = 2; i < argc; i++) { - if (!vis_window_new(vis, argv[i])) - return 1; - } - - struct timeval idle = { .tv_usec = 0 }, *timeout = NULL; - Key key, key_prev, *key_mod = NULL; - - while (vis->running) { - if (screen.need_resize) { - resize_screen(&screen); - vis_resize(vis, screen.w, screen.h); - } - - fd_set fds; - FD_ZERO(&fds); - FD_SET(STDIN_FILENO, &fds); - - vis_update(vis); - 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)) { - if (mode->idle) - mode->idle(); - timeout = NULL; - continue; - } - - key = getkey(); - 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 (key.code) - continue; - - if (mode->input) - mode->input(key.str, strlen(key.str)); - if (mode->idle) - timeout = &idle; - } - - vis_free(vis); - cleanup(); - return 0; -} diff --git a/util.h b/util.h index 2992bea..b2446a7 100644 --- a/util.h +++ b/util.h @@ -1,3 +1,6 @@ +#ifndef UTIL_H +#define UTIL_H + #define LENGTH(x) ((int)(sizeof (x) / sizeof *(x))) #define MIN(a, b) ((a) > (b) ? (b) : (a)) #define MAX(a, b) ((a) < (b) ? (b) : (a)) @@ -5,3 +8,5 @@ /* is c the start of a utf8 sequence? */ #define isutf8(c) (((c)&0xC0)!=0x80) #define ISASCII(ch) ((unsigned char)ch < 0x80) + +#endif diff --git a/vis.c b/vis.c index 9d7c3a9..bc091c3 100644 --- a/vis.c +++ b/vis.c @@ -1,483 +1,1002 @@ -#define _BSD_SOURCE +#define _POSIX_SOURCE +#include #include +#include #include -#include "vis.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "editor.h" #include "util.h" -static VisWin *vis_window_new_text(Vis *vis, Text *text); -static void vis_window_free(Vis *vis, VisWin *win); -static void vis_window_split_internal(Vis *vis, const char *filename); -static void vis_windows_invalidate(Vis *vis, size_t start, size_t end); -static void vis_window_draw(VisWin *win); -static void vis_windows_arrange_horizontal(Vis *vis); -static void vis_windows_arrange_vertical(Vis *vis); - -static Prompt *vis_prompt_new(); -static void vis_prompt_free(Prompt *prompt); -static void vis_prompt_clear(Prompt *prompt); -static void vis_prompt_resize(Prompt *prompt, int width, int height); -static void vis_prompt_move(Prompt *prompt, int x, int y); -static void vis_prompt_draw(Prompt *prompt); -static void vis_prompt_update(Prompt *prompt); - -static void vis_window_resize(VisWin *win, int width, int height) { - window_resize(win->win, width, win->statuswin ? height - 1 : height); - if (win->statuswin) - wresize(win->statuswin, 1, width); - win->width = width; - win->height = height; -} - -static void vis_window_move(VisWin *win, int x, int y) { - window_move(win->win, x, y); - if (win->statuswin) - mvwin(win->statuswin, y + win->height - 1, x); -} - -static void vis_window_statusbar_draw(VisWin *win) { - size_t line, col; - if (win->statuswin && win->vis->statusbar) { - window_cursor_getxy(win->win, &line, &col); - win->vis->statusbar(win->statuswin, win->vis->win == win, - text_filename(win->text), line, col); - } +#ifdef PDCURSES +int ESCDELAY; +#endif +#ifndef NCURSES_REENTRANT +# define set_escdelay(d) (ESCDELAY = (d)) +#endif + +typedef union { + bool b; + size_t i; + const char *s; + size_t (*m)(Win*); + 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; + bool common_prefix; + void (*enter)(Mode *old); + void (*leave)(Mode *new); + bool (*unknown)(Key *key0, Key *key1); /* unknown key for this mode, return value determines whether parent modes will be checked */ + void (*input)(const char *str, size_t len); /* unknown key for this an all parent modes */ + void (*idle)(void); +}; + +typedef struct { + char *name; + Mode *mode; + editor_statusbar_t statusbar; +} Config; + +typedef struct { + int count; + Register *reg; + Filerange range; + size_t pos; + bool linewise; +} OperatorContext; + +typedef struct { + void (*func)(OperatorContext*); /* function implementing the operator logic */ + bool selfcontained; /* is this operator followed by movements/text-objects */ +} Operator; + +typedef struct { + size_t (*cmd)(const Arg*); + size_t (*win)(Win*); + size_t (*txt)(Text*, size_t pos); + enum { + LINEWISE = 1 << 0, + CHARWISE = 1 << 1, + INCLUSIVE = 1 << 2, + EXCLUSIVE = 1 << 3, + } type; + int count; +} Movement; + +typedef struct { + Filerange (*range)(Text*, size_t pos); + enum { + INNER, + OUTER, + } type; +} TextObject; + +typedef struct { + int count; + bool linewise; + Operator *op; + Movement *movement; + TextObject *textobj; + Register *reg; + Mark mark; + Key key; + Arg arg; +} Action; + +typedef struct { + const char *name; + bool (*cmd)(const char *argv[]); + regex_t regex; +} Command; + +/* global variables */ +static Editor *vis; /* global editor instance, keeps track of all windows etc. */ +static Mode *mode; /* currently active mode, used to search for keybindings */ +static Mode *mode_prev; /* mode which was active previously */ +static Action action; /* current action which is in progress */ +static Action action_prev; /* last operator action used by the repeat '.' key */ + +/* movements which can be used besides the one in text-motions.h and window.h */ +static size_t search_forward(const Arg *arg); +static size_t search_backward(const Arg *arg); +static size_t mark_goto(const Arg *arg); +static size_t mark_line_goto(const Arg *arg); +static size_t to(const Arg *arg); +static size_t till(const Arg *arg); +static size_t to_left(const Arg *arg); +static size_t till_left(const Arg *arg); +static size_t line(const Arg *arg); +static size_t column(const Arg *arg); + +enum { + MOVE_LINE_UP, + MOVE_LINE_DOWN, + MOVE_LINE_BEGIN, + MOVE_LINE_START, + MOVE_LINE_FINISH, + MOVE_LINE_END, + MOVE_LINE, + MOVE_COLUMN, + MOVE_CHAR_PREV, + MOVE_CHAR_NEXT, + MOVE_WORD_START_PREV, + MOVE_WORD_START_NEXT, + MOVE_WORD_END_PREV, + MOVE_WORD_END_NEXT, + MOVE_SENTENCE_PREV, + MOVE_SENTENCE_NEXT, + MOVE_PARAGRAPH_PREV, + MOVE_PARAGRAPH_NEXT, + MOVE_BRACKET_MATCH, + MOVE_LEFT_TO, + MOVE_RIGHT_TO, + MOVE_LEFT_TILL, + MOVE_RIGHT_TILL, + MOVE_FILE_BEGIN, + MOVE_FILE_END, + MOVE_MARK, + MOVE_MARK_LINE, + MOVE_SEARCH_FORWARD, + MOVE_SEARCH_BACKWARD, +}; + +static Movement moves[] = { + [MOVE_LINE_UP] = { .win = window_line_up }, + [MOVE_LINE_DOWN] = { .win = window_line_down }, + [MOVE_LINE_BEGIN] = { .txt = text_line_begin, .type = LINEWISE }, + [MOVE_LINE_START] = { .txt = text_line_start, .type = LINEWISE }, + [MOVE_LINE_FINISH] = { .txt = text_line_finish, .type = LINEWISE }, + [MOVE_LINE_END] = { .txt = text_line_end, .type = LINEWISE }, + [MOVE_LINE] = { .cmd = line, .type = LINEWISE }, + [MOVE_COLUMN] = { .cmd = column, .type = CHARWISE }, + [MOVE_CHAR_PREV] = { .win = window_char_prev }, + [MOVE_CHAR_NEXT] = { .win = window_char_next }, + [MOVE_WORD_START_PREV] = { .txt = text_word_start_prev, .type = CHARWISE }, + [MOVE_WORD_START_NEXT] = { .txt = text_word_start_next, .type = CHARWISE }, + [MOVE_WORD_END_PREV] = { .txt = text_word_end_prev, .type = CHARWISE|INCLUSIVE }, + [MOVE_WORD_END_NEXT] = { .txt = text_word_end_next, .type = CHARWISE|INCLUSIVE }, + [MOVE_SENTENCE_PREV] = { .txt = text_sentence_prev, .type = LINEWISE }, + [MOVE_SENTENCE_NEXT] = { .txt = text_sentence_next, .type = LINEWISE }, + [MOVE_PARAGRAPH_PREV] = { .txt = text_paragraph_prev, .type = LINEWISE }, + [MOVE_PARAGRAPH_NEXT] = { .txt = text_paragraph_next, .type = LINEWISE }, + [MOVE_BRACKET_MATCH] = { .txt = text_bracket_match, .type = LINEWISE|INCLUSIVE }, + [MOVE_FILE_BEGIN] = { .txt = text_begin, .type = LINEWISE }, + [MOVE_FILE_END] = { .txt = text_end, .type = LINEWISE }, + [MOVE_LEFT_TO] = { .cmd = to_left, .type = LINEWISE }, + [MOVE_RIGHT_TO] = { .cmd = to, .type = LINEWISE }, + [MOVE_LEFT_TILL] = { .cmd = till_left, .type = LINEWISE }, + [MOVE_RIGHT_TILL] = { .cmd = till, .type = LINEWISE }, + [MOVE_MARK] = { .cmd = mark_goto, .type = LINEWISE }, + [MOVE_MARK_LINE] = { .cmd = mark_line_goto, .type = LINEWISE }, + [MOVE_SEARCH_FORWARD] = { .cmd = search_forward, .type = LINEWISE }, + [MOVE_SEARCH_BACKWARD] = { .cmd = search_backward, .type = LINEWISE }, +}; + +/* available text objects */ +enum { + TEXT_OBJ_WORD, + TEXT_OBJ_LINE_UP, + TEXT_OBJ_LINE_DOWN, + TEXT_OBJ_SENTENCE, + TEXT_OBJ_PARAGRAPH, + TEXT_OBJ_OUTER_SQUARE_BRACKET, + TEXT_OBJ_INNER_SQUARE_BRACKET, + TEXT_OBJ_OUTER_CURLY_BRACKET, + TEXT_OBJ_INNER_CURLY_BRACKET, + TEXT_OBJ_OUTER_ANGLE_BRACKET, + TEXT_OBJ_INNER_ANGLE_BRACKET, + TEXT_OBJ_OUTER_PARANTHESE, + TEXT_OBJ_INNER_PARANTHESE, + TEXT_OBJ_OUTER_QUOTE, + TEXT_OBJ_INNER_QUOTE, + TEXT_OBJ_OUTER_SINGLE_QUOTE, + TEXT_OBJ_INNER_SINGLE_QUOTE, + TEXT_OBJ_OUTER_BACKTICK, + TEXT_OBJ_INNER_BACKTICK, +}; + +static TextObject textobjs[] = { + [TEXT_OBJ_WORD] = { text_object_word }, + [TEXT_OBJ_LINE_UP] = { text_object_line }, + [TEXT_OBJ_LINE_DOWN] = { text_object_line }, + [TEXT_OBJ_SENTENCE] = { text_object_sentence }, + [TEXT_OBJ_PARAGRAPH] = { text_object_paragraph }, + [TEXT_OBJ_OUTER_SQUARE_BRACKET] = { text_object_square_bracket, OUTER }, + [TEXT_OBJ_INNER_SQUARE_BRACKET] = { text_object_square_bracket, INNER }, + [TEXT_OBJ_OUTER_CURLY_BRACKET] = { text_object_curly_bracket, OUTER }, + [TEXT_OBJ_INNER_CURLY_BRACKET] = { text_object_curly_bracket, INNER }, + [TEXT_OBJ_OUTER_ANGLE_BRACKET] = { text_object_angle_bracket, OUTER }, + [TEXT_OBJ_INNER_ANGLE_BRACKET] = { text_object_angle_bracket, INNER }, + [TEXT_OBJ_OUTER_PARANTHESE] = { text_object_paranthese, OUTER }, + [TEXT_OBJ_INNER_PARANTHESE] = { text_object_paranthese, INNER }, + [TEXT_OBJ_OUTER_QUOTE] = { text_object_quote, OUTER }, + [TEXT_OBJ_INNER_QUOTE] = { text_object_quote, INNER }, + [TEXT_OBJ_OUTER_SINGLE_QUOTE] = { text_object_single_quote, OUTER }, + [TEXT_OBJ_INNER_SINGLE_QUOTE] = { text_object_single_quote, INNER }, + [TEXT_OBJ_OUTER_BACKTICK] = { text_object_backtick, OUTER }, + [TEXT_OBJ_INNER_BACKTICK] = { text_object_backtick, INNER }, +}; + +/* if some movements are forced to be linewise, they are translated to text objects */ +static TextObject *moves_linewise[] = { + [MOVE_LINE_UP] = &textobjs[TEXT_OBJ_LINE_UP], + [MOVE_LINE_DOWN] = &textobjs[TEXT_OBJ_LINE_DOWN], +}; + +/* functions to be called from keybindings */ +static void switchmode(const Arg *arg); +static void mark_set(const Arg *arg); +static void insert(const Arg *arg); +static void insert_tab(const Arg *arg); +static void insert_newline(const Arg *arg); +static void split(const Arg *arg); +static void quit(const Arg *arg); +static void repeat(const Arg *arg); +static void count(const Arg *arg); +static void linewise(const Arg *arg); +static void operator(const Arg *arg); +static void movement_key(const Arg *arg); +static void movement(const Arg *arg); +static void textobj(const Arg *arg); +static void reg(const Arg *arg); +static void mark(const Arg *arg); +static void mark_line(const Arg *arg); +static void undo(const Arg *arg); +static void redo(const Arg *arg); +static void zero(const Arg *arg); +static void delete_word(const Arg *arg); +static void insert_register(const Arg *arg); +static void prompt(const Arg *arg); +static void prompt_enter(const Arg *arg); +static void prompt_up(const Arg *arg); +static void prompt_down(const Arg *arg); +static void insert_verbatim(const Arg *arg); +static void cursor(const Arg *arg); +static void call(const Arg *arg); + +/* commands to enter at the ':'-prompt */ +static bool cmd_gotoline(const char *argv[]); +static bool cmd_open(const char *argv[]); +static bool cmd_quit(const char *argv[]); +static bool cmd_read(const char *argv[]); +static bool cmd_substitute(const char *argv[]); +static bool cmd_split(const char *argv[]); +static bool cmd_vsplit(const char *argv[]); +static bool cmd_wq(const char *argv[]); +static bool cmd_write(const char *argv[]); + +static void action_reset(Action *a); +static void switchmode_to(Mode *new_mode); + +#include "config.h" + +static Key getkey(void); +static void action_do(Action *a); +static bool exec_command(char *cmdline); + +/* operator implementations of type: void (*op)(OperatorContext*) */ + +static void op_delete(OperatorContext *c) { + size_t len = c->range.end - c->range.start; + c->reg->linewise = c->linewise; + register_put(c->reg, vis->win->text, &c->range); + editor_delete(vis, c->range.start, len); + window_cursor_to(vis->win->win, c->range.start); +} + +static void op_change(OperatorContext *c) { + op_delete(c); + switchmode(&(const Arg){ .i = VIS_MODE_INSERT }); +} + +static void op_yank(OperatorContext *c) { + c->reg->linewise = c->linewise; + register_put(c->reg, vis->win->text, &c->range); } -static void vis_window_cursor_moved(Win *win, void *data) { - vis_window_statusbar_draw(data); +static void op_paste(OperatorContext *c) { + size_t pos = window_cursor_get(vis->win->win); + if (c->reg->linewise) + pos = text_line_next(vis->win->text, pos); + editor_insert(vis, pos, c->reg->data, c->reg->len); + window_cursor_to(vis->win->win, pos + c->reg->len); } -void vis_statusbar_set(Vis *vis, vis_statusbar_t statusbar) { - vis->statusbar = statusbar; +/* movement implementations of type: size_t (*move)(const Arg*) */ + +static size_t search_forward(const Arg *arg) { + size_t pos = window_cursor_get(vis->win->win); + return text_search_forward(vis->win->text, pos, vis->search_pattern); } -static void vis_windows_arrange_horizontal(Vis *vis) { - int n = 0, x = 0, y = 0; - for (VisWin *win = vis->windows; win; win = win->next) - n++; - int height = vis->height / n; - for (VisWin *win = vis->windows; win; win = win->next) { - vis_window_resize(win, vis->width, win->next ? height : vis->height - y); - vis_window_move(win, x, y); - y += height; - } +static size_t search_backward(const Arg *arg) { + size_t pos = window_cursor_get(vis->win->win); + return text_search_backward(vis->win->text, pos, vis->search_pattern); } -static void vis_windows_arrange_vertical(Vis *vis) { - int n = 0, x = 0, y = 0; - for (VisWin *win = vis->windows; win; win = win->next) - n++; - int width = vis->width / n - 1; - for (VisWin *win = vis->windows; win; win = win->next) { - vis_window_resize(win, win->next ? width : vis->width - x, vis->height); - vis_window_move(win, x, y); - x += width; - if (win->next) - mvvline(0, x++, ACS_VLINE, vis->height); - } +static void mark_set(const Arg *arg) { + text_mark_set(vis->win->text, arg->i, window_cursor_get(vis->win->win)); } -static void vis_window_split_internal(Vis *vis, const char *filename) { - VisWin *sel = vis->win; - if (filename) { - // TODO? move this to vis_window_new - sel = NULL; - for (VisWin *w = vis->windows; w; w = w->next) { - const char *f = text_filename(w->text); - if (f && strcmp(f, filename) == 0) { - sel = w; - break; - } - } - } - if (sel) { - VisWin *win = vis_window_new_text(vis, sel->text); - win->text = sel->text; - window_syntax_set(win->win, window_syntax_get(sel->win)); - window_cursor_to(win->win, window_cursor_get(sel->win)); - } else { - vis_window_new(vis, filename); - } +static size_t mark_goto(const Arg *arg) { + return text_mark_get(vis->win->text, action.mark); } -void vis_window_split(Vis *vis, const char *filename) { - vis_window_split_internal(vis, filename); - vis->windows_arrange = vis_windows_arrange_horizontal; - vis_draw(vis); +static size_t mark_line_goto(const Arg *arg) { + return text_line_start(vis->win->text, mark_goto(arg)); } -void vis_window_vsplit(Vis *vis, const char *filename) { - vis_window_split_internal(vis, filename); - vis->windows_arrange = vis_windows_arrange_vertical; - vis_draw(vis); +static size_t to(const Arg *arg) { + return text_find_char_next(vis->win->text, window_cursor_get(vis->win->win) + 1, + action.key.str, strlen(action.key.str)); } -void vis_resize(Vis *vis, int width, int height) { - vis->width = width; - vis->height = height; - if (vis->prompt->active) { - vis->height--; - vis_prompt_resize(vis->prompt, vis->width, 1); - vis_prompt_move(vis->prompt, 0, vis->height); - vis_prompt_draw(vis->prompt); - } - vis_draw(vis); +static size_t till(const Arg *arg) { + return text_char_prev(vis->win->text, to(arg)); } -void vis_window_next(Vis *vis) { - VisWin *sel = vis->win; - if (!sel) +static size_t to_left(const Arg *arg) { + return text_find_char_prev(vis->win->text, window_cursor_get(vis->win->win) - 1, + action.key.str, strlen(action.key.str)); +} + +static size_t till_left(const Arg *arg) { + return text_char_next(vis->win->text, to_left(arg)); +} + +static size_t line(const Arg *arg) { + if (action.count == 0) + return text_size(vis->win->text); + size_t pos = text_pos_by_lineno(vis->win->text, action.count); + action.count = 0; + return pos; +} + +static size_t column(const Arg *arg) { + char c; + EditorWin *win = vis->win; + size_t pos = window_cursor_get(win->win); + Iterator it = text_iterator_get(win->text, text_line_begin(win->text, pos)); + while (action.count-- > 0 && text_iterator_byte_get(&it, &c) && c != '\n') + text_iterator_byte_next(&it, NULL); + action.count = 0; + return it.pos; +} + +/* key bindings functions of type: void (*func)(const Arg*) */ + +static void repeat(const Arg *arg) { + action = action_prev; + action_do(&action); +} + +static void count(const Arg *arg) { + action.count = action.count * 10 + arg->i; +} + +static void linewise(const Arg *arg) { + action.linewise = arg->b; +} + +static void operator(const Arg *arg) { + Operator *op = &ops[arg->i]; + if (mode == &vis_modes[VIS_MODE_VISUAL]) { + action.op = op; + action_do(&action); return; - vis->win = vis->win->next; - if (!vis->win) - vis->win = vis->windows; - vis_window_statusbar_draw(sel); - vis_window_statusbar_draw(vis->win); + } + + switchmode(&(const Arg){ .i = VIS_MODE_OPERATOR }); + if (action.op == op) { + /* hacky way to handle double operators i.e. things like + * dd, yy etc where the second char isn't a movement */ + action.linewise = true; + action.textobj = moves_linewise[MOVE_LINE_DOWN]; + action_do(&action); + } else { + action.op = op; + if (op->selfcontained) + action_do(&action); + } } -void vis_window_prev(Vis *vis) { - VisWin *sel = vis->win; - if (!sel) +static void movement_key(const Arg *arg) { + Key k = getkey(); + if (!k.str[0]) { + action_reset(&action); return; - vis->win = vis->win->prev; - if (!vis->win) - for (vis->win = vis->windows; vis->win->next; vis->win = vis->win->next); - vis_window_statusbar_draw(sel); - vis_window_statusbar_draw(vis->win); -} - -static void vis_windows_invalidate(Vis *vis, size_t start, size_t end) { - for (VisWin *win = vis->windows; win; win = win->next) { - if (vis->win != win && vis->win->text == win->text) { - Filerange view = window_viewport_get(win->win); - if ((view.start <= start && start <= view.end) || - (view.start <= end && end <= view.end)) - vis_window_draw(win); - } } - vis_window_draw(vis->win); + action.key = k; + action.movement = &moves[arg->i]; + action_do(&action); } +static void movement(const Arg *arg) { + if (action.linewise && arg->i < LENGTH(moves_linewise)) + action.textobj = moves_linewise[arg->i]; + else + action.movement = &moves[arg->i]; + action_do(&action); +} -bool vis_syntax_load(Vis *vis, Syntax *syntaxes, Color *colors) { - bool success = true; - vis->syntaxes = syntaxes; - for (Syntax *syn = syntaxes; syn && syn->name; syn++) { - if (regcomp(&syn->file_regex, syn->file, REG_EXTENDED|REG_NOSUB|REG_ICASE|REG_NEWLINE)) - success = false; - Color *color = colors; - for (int j = 0; j < LENGTH(syn->rules); j++) { - SyntaxRule *rule = &syn->rules[j]; - if (!rule->rule) - break; - if (rule->color.fg == 0 && color && color->fg != 0) - rule->color = *color++; - if (rule->color.attr == 0) - rule->color.attr = A_NORMAL; - if (rule->color.fg != 0) - rule->color.attr |= COLOR_PAIR(vis_color_reserve(rule->color.fg, rule->color.bg)); - if (regcomp(&rule->regex, rule->rule, REG_EXTENDED|rule->cflags)) - success = false; - } - } +static void textobj(const Arg *arg) { + action.textobj = &textobjs[arg->i]; + action_do(&action); +} - return success; +static void reg(const Arg *arg) { + action.reg = &vis->registers[arg->i]; } -void vis_syntax_unload(Vis *vis) { - for (Syntax *syn = vis->syntaxes; syn && syn->name; syn++) { - regfree(&syn->file_regex); - for (int j = 0; j < LENGTH(syn->rules); j++) { - SyntaxRule *rule = &syn->rules[j]; - if (!rule->rule) - break; - regfree(&rule->regex); +static void mark(const Arg *arg) { + action.mark = arg->i; + action.movement = &moves[MOVE_MARK]; + action_do(&action); +} + +static void mark_line(const Arg *arg) { + action.mark = arg->i; + action.movement = &moves[MOVE_MARK_LINE]; + action_do(&action); +} + +static void undo(const Arg *arg) { + if (text_undo(vis->win->text)) + editor_draw(vis); +} + +static void redo(const Arg *arg) { + if (text_redo(vis->win->text)) + editor_draw(vis); +} + +static void zero(const Arg *arg) { + if (action.count == 0) + movement(&(const Arg){ .i = MOVE_LINE_BEGIN }); + else + count(&(const Arg){ .i = 0 }); +} + +static void delete_word(const Arg *arg) { + operator(&(const Arg){ .i = OP_DELETE }); + movement(&(const Arg){ .i = MOVE_WORD_START_PREV }); +} + +static void insert_register(const Arg *arg) { + Register *reg = &vis->registers[arg->i]; + editor_insert(vis, window_cursor_get(vis->win->win), reg->data, reg->len); +} + +static void prompt(const Arg *arg) { + editor_prompt_show(vis, arg->s); + switchmode(&(const Arg){ .i = VIS_MODE_PROMPT }); +} + +static void prompt_enter(const Arg *arg) { + char *s = editor_prompt_get(vis); + switchmode(&(const Arg){ .i = VIS_MODE_NORMAL }); + switch (vis->prompt->title[0]) { + case '/': + case '?': + if (text_regex_compile(vis->search_pattern, s, REG_EXTENDED)) { + action_reset(&action); + } else { + movement(&(const Arg){ .i = vis->prompt->title[0] == '/' ? + MOVE_SEARCH_FORWARD : MOVE_SEARCH_BACKWARD }); } + break; + case ':': + exec_command(s); + break; } + free(s); +} - vis->syntaxes = NULL; +static void prompt_up(const Arg *arg) { + } -static void vis_window_draw(VisWin *win) { - // TODO window_draw draw should restore cursor position - window_draw(win->win); - window_cursor_to(win->win, window_cursor_get(win->win)); +static void prompt_down(const Arg *arg) { + } -void vis_draw(Vis *vis) { - erase(); - vis->windows_arrange(vis); - for (VisWin *win = vis->windows; win; win = win->next) { - if (vis->win != win) - vis_window_draw(win); +static void insert_verbatim(const Arg *arg) { + int value = 0; + for (int i = 0; i < 3; i++) { + Key k = getkey(); + if (k.str[0] < '0' || k.str[0] > '9') + return; + value = value * 10 + k.str[0] - '0'; } - vis_window_draw(vis->win); - wnoutrefresh(stdscr); + char v = value; + editor_insert(vis, window_cursor_get(vis->win->win), &v, 1); } -void vis_update(Vis *vis) { - for (VisWin *win = vis->windows; win; win = win->next) { - if (vis->win != win) { - if (win->statuswin) - wnoutrefresh(win->statuswin); - window_update(win->win); - } - } +static void quit(const Arg *arg) { + vis->running = false; +} - if (vis->win->statuswin) - wnoutrefresh(vis->win->statuswin); - if (vis->prompt && vis->prompt->active) - vis_prompt_update(vis->prompt); - window_update(vis->win->win); +static void split(const Arg *arg) { + editor_window_split(vis, arg->s); } -static void vis_window_free(Vis *vis, VisWin *win) { - if (!win) - return; - window_free(win->win); - if (win->statuswin) - delwin(win->statuswin); - bool needed = false; - for (VisWin *w = vis ? vis->windows : NULL; w; w = w->next) { - if (w->text == win->text) { - needed = true; - break; +static void cursor(const Arg *arg) { + arg->m(vis->win->win); +} + +static void call(const Arg *arg) { + arg->f(vis); +} + +static void insert(const Arg *arg) { + editor_insert_key(vis, arg->s, arg->s ? strlen(arg->s) : 0); +} + +static void insert_tab(const Arg *arg) { + insert(&(const Arg){ .s = "\t" }); +} + +static void insert_newline(const Arg *arg) { + // TODO determine file type to insert \n\r or \n + insert(&(const Arg){ .s = "\n" }); +} + +static void switchmode(const Arg *arg) { + switchmode_to(&vis_modes[arg->i]); +} + +/* action processing, executed the operator / movement / text object */ + +static void action_do(Action *a) { + Text *txt = vis->win->text; + Win *win = vis->win->win; + size_t pos = window_cursor_get(win); + if (a->count == 0) + a->count = 1; + OperatorContext c = { + .count = a->count, + .pos = pos, + .reg = a->reg ? a->reg : &vis->registers[REG_DEFAULT], + .linewise = a->linewise, + }; + + if (a->movement) { + size_t start = pos; + for (int i = 0; i < a->count; i++) { + if (a->movement->txt) + pos = a->movement->txt(txt, pos); + else if (a->movement->win) + pos = a->movement->win(win); + else + pos = a->movement->cmd(&a->arg); + if (pos == (size_t)-1) + break; } - } - if (!needed) - text_free(win->text); - free(win); -} - -static VisWin *vis_window_new_text(Vis *vis, Text *text) { - VisWin *win = calloc(1, sizeof(VisWin)); - if (!win) - return NULL; - win->vis = vis; - win->text = text; - win->win = window_new(win->text); - win->statuswin = newwin(1, vis->width, 0, 0); - if (!win->win || !win->statuswin) { - vis_window_free(vis, win); - return NULL; - } - window_cursor_watch(win->win, vis_window_cursor_moved, win); - if (vis->windows) - vis->windows->prev = win; - win->next = vis->windows; - win->prev = NULL; - vis->windows = win; - vis->win = win; - return win; -} - -bool vis_window_new(Vis *vis, const char *filename) { - Text *text = text_load(filename); - if (!text) - return false; - VisWin *win = vis_window_new_text(vis, text); - if (!win) { - text_free(text); - return false; - } + if (pos == (size_t)-1) { + c.range.start = start; + c.range.end = start; + } else { + c.range.start = MIN(start, pos); + c.range.end = MAX(start, pos); + } - if (filename) { - for (Syntax *syn = vis->syntaxes; syn && syn->name; syn++) { - if (!regexec(&syn->file_regex, filename, 0, NULL, 0)) { - window_syntax_set(win->win, syn); + if (!a->op) { + if (a->movement->type & CHARWISE) + window_scroll_to(win, pos); + else + window_cursor_to(win, pos); + } else if (a->movement->type & INCLUSIVE) { + Iterator it = text_iterator_get(txt, c.range.end); + text_iterator_char_next(&it, NULL); + c.range.end = it.pos; + } + } else if (a->textobj) { + Filerange r; + c.range.start = c.range.end = pos; + for (int i = 0; i < a->count; i++) { + r = a->textobj->range(txt, pos); + // TODO range_valid? + if (r.start == (size_t)-1 || r.end == (size_t)-1) break; + if (a->textobj->type == OUTER) { + r.start--; + r.end++; + } + // TODO c.range = range_union(&c.range, &r); + c.range.start = MIN(c.range.start, r.start); + c.range.end = MAX(c.range.end, r.end); + if (i < a->count - 1) { + if (a->textobj == &textobjs[TEXT_OBJ_LINE_UP]) { + pos = c.range.start - 1; + } else { + pos = c.range.end + 1; + } } } + } else if (mode == &vis_modes[VIS_MODE_VISUAL]) { + c.range = window_selection_get(win); + if (c.range.start == (size_t)-1 || c.range.end == (size_t)-1) + c.range.start = c.range.end = pos; } - - vis_draw(vis); - return true; + if (a->op) { + a->op->func(&c); + if (mode == &vis_modes[VIS_MODE_OPERATOR]) + switchmode_to(mode_prev); + else if (mode == &vis_modes[VIS_MODE_VISUAL]) + switchmode(&(const Arg){ .i = VIS_MODE_NORMAL }); + text_snapshot(txt); + } + + if (a != &action_prev) { + if (a->op) + action_prev = *a; + action_reset(a); + } } -static void vis_window_detach(Vis *vis, VisWin *win) { - if (win->prev) - win->prev->next = win->next; - if (win->next) - win->next->prev = win->prev; - if (vis->windows == win) - vis->windows = win->next; - win->next = win->prev = NULL; -} - -void vis_window_close(Vis *vis) { - VisWin *win = vis->win; - vis->win = win->next ? win->next : win->prev; - vis_window_detach(vis, win); - vis_window_free(vis, win); -} - -Vis *vis_new(int width, int height) { - Vis *vis = calloc(1, sizeof(Vis)); - if (!vis) - return NULL; - if (!(vis->prompt = vis_prompt_new())) - goto err; - if (!(vis->search_pattern = text_regex_new())) - goto err; - vis->width = width; - vis->height = height; - vis->windows_arrange = vis_windows_arrange_horizontal; - vis->running = true; - return vis; -err: - vis_free(vis); - return NULL; +static void action_reset(Action *a) { + a->count = 0; + a->linewise = false; + a->op = NULL; + a->movement = NULL; + a->textobj = NULL; + a->reg = NULL; } -void vis_free(Vis *vis) { - while (vis->windows) - vis_window_close(vis); - vis_prompt_free(vis->prompt); - text_regex_free(vis->search_pattern); - for (int i = 0; i < REG_LAST; i++) - register_free(&vis->registers[i]); - vis_syntax_unload(vis); - free(vis); +static void switchmode_to(Mode *new_mode) { + if (mode == new_mode) + return; + if (mode->leave) + mode->leave(new_mode); + mode_prev = mode; + //fprintf(stderr, "%s -> %s\n", mode_prev->name, new_mode->name); + mode = new_mode; + if (mode->enter) + mode->enter(mode_prev); + // TODO display mode name somewhere? + } -void vis_insert_key(Vis *vis, const char *c, size_t len) { - Win *win = vis->win->win; - size_t start = window_cursor_get(win); - window_insert_key(win, c, len); - vis_windows_invalidate(vis, start, start + len); + + +/* ':'-command implementations */ + +static bool cmd_gotoline(const char *argv[]) { + action.count = strtoul(argv[0], NULL, 10); + movement(&(const Arg){ .i = MOVE_LINE }); + return true; } -void vis_replace_key(Vis *vis, const char *c, size_t len) { - Win *win = vis->win->win; - size_t start = window_cursor_get(win); - window_replace_key(win, c, len); - vis_windows_invalidate(vis, start, start + 6); +static bool cmd_open(const char *argv[]) { + for (const char **file = &argv[1]; *file; file++) + editor_window_new(vis, *file); + return true; } -void vis_backspace_key(Vis *vis) { - Win *win = vis->win->win; - size_t end = window_cursor_get(win); - size_t start = window_backspace_key(win); - vis_windows_invalidate(vis, start, end); +static bool cmd_quit(const char *argv[]) { + bool force = strchr(argv[0], '!') != NULL; + for (EditorWin *win = vis->windows; win; win = win->next) { + if (text_modified(win->text) && !force) + return false; + } + vis->running = false; + return true; } -void vis_delete_key(Vis *vis) { - size_t start = window_delete_key(vis->win->win); - vis_windows_invalidate(vis, start, start + 6); +static bool cmd_read(const char *argv[]) { + size_t pos = window_cursor_get(vis->win->win); + for (const char **file = &argv[1]; *file; file++) { + int fd = open(*file, O_RDONLY); + char *data = NULL; + struct stat info; + if (fd == -1) + goto err; + if (fstat(fd, &info) == -1) + goto err; + if (!S_ISREG(info.st_mode)) + goto err; + // XXX: use lseek(fd, 0, SEEK_END); instead? + data = mmap(NULL, info.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + goto err; + + text_insert_raw(vis->win->text, pos, data, info.st_size); + pos += info.st_size; + err: + if (fd > 2) + close(fd); + if (data && data != MAP_FAILED) + munmap(data, info.st_size); + } + editor_draw(vis); + return true; } -void vis_insert(Vis *vis, size_t pos, const char *c, size_t len) { - text_insert_raw(vis->win->text, pos, c, len); - vis_windows_invalidate(vis, pos, pos + len); +static bool cmd_substitute(const char *argv[]) { + // TODO + return true; } -void vis_delete(Vis *vis, size_t pos, size_t len) { - text_delete(vis->win->text, pos, len); - vis_windows_invalidate(vis, pos, pos + len); +static bool cmd_split(const char *argv[]) { + editor_window_split(vis, argv[1]); + for (const char **file = &argv[2]; *file; file++) + editor_window_split(vis, *file); + return true; } +static bool cmd_vsplit(const char *argv[]) { + editor_window_vsplit(vis, argv[1]); + for (const char **file = &argv[2]; *file; file++) + editor_window_vsplit(vis, *file); + return true; +} -static void vis_prompt_free(Prompt *prompt) { - if (!prompt) - return; - vis_window_free(NULL, prompt->win); - if (prompt->titlewin) - delwin(prompt->titlewin); - free(prompt->title); - free(prompt); +static bool cmd_wq(const char *argv[]) { + if (cmd_write(argv)) + return cmd_quit(argv); + return false; } -static Prompt *vis_prompt_new() { - Text *text = text_load(NULL); - if (!text) - return NULL; - Prompt *prompt = calloc(1, sizeof(Prompt)); - if (!prompt) - goto err; +static bool cmd_write(const char *argv[]) { + Text *text = vis->win->text; + if (!argv[1]) + argv[1] = text_filename(text); + for (const char **file = &argv[1]; *file; file++) { + if (text_save(text, *file)) + return false; + } + return true; +} - if (!(prompt->win = calloc(1, sizeof(VisWin)))) - goto err; +static bool exec_command(char *cmdline) { + static bool init = false; + if (!init) { + /* compile the regexes on first inovaction */ + for (Command *c = cmds; c->name; c++) + regcomp(&c->regex, c->name, REG_EXTENDED); + init = true; + } - if (!(prompt->win->win = window_new(text))) - goto err; + Command *cmd = NULL; + for (Command *c = cmds; c->name; c++) { + if (!regexec(&c->regex, cmdline, 0, NULL, 0)) { + cmd = c; + break; + } + } - prompt->win->text = text; + if (!cmd) + return false; - if (!(prompt->titlewin = newwin(0, 0, 0, 0))) - goto err; + const char *argv[32] = { cmdline }; + char *s = cmdline; + for (int i = 1; i < LENGTH(argv); i++) { + if (s) { + if ((s = strchr(s, ' '))) + *s++ = '\0'; + } + while (s && *s && *s == ' ') + s++; + argv[i] = s ? s : NULL; + } - return prompt; -err: - if (!prompt || !prompt->win) - text_free(text); - vis_prompt_free(prompt); - return NULL; + cmd->cmd(argv); + return true; } -static void vis_prompt_resize(Prompt *prompt, int width, int height) { - size_t title_width = strlen(prompt->title); - wresize(prompt->titlewin, height, title_width); - vis_window_resize(prompt->win, width - title_width, height); -} +/* default editor configuration to use */ +static Config *config = &editors[0]; -static void vis_prompt_move(Prompt *prompt, int x, int y) { - size_t title_width = strlen(prompt->title); - mvwin(prompt->titlewin, y, x); - vis_window_move(prompt->win, x + title_width, y); -} -void vis_prompt_show(Vis *vis, const char *title) { - Prompt *prompt = vis->prompt; - if (prompt->active) - return; - prompt->active = true; - prompt->editor = vis->win; - vis->win = prompt->win; - free(prompt->title); - prompt->title = strdup(title); - vis_resize(vis, vis->width, vis->height); -} +typedef struct Screen Screen; +static struct Screen { + int w, h; + bool need_resize; +} screen = { .need_resize = true }; -static void vis_prompt_draw(Prompt *prompt) { - mvwaddstr(prompt->titlewin, 0, 0, prompt->title); +static void sigwinch_handler(int sig) { + screen.need_resize = true; } -static void vis_prompt_update(Prompt *prompt) { - wnoutrefresh(prompt->titlewin); +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() { + endwin(); + //delscreen(set_term(NULL)); +} + +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) { + if (mode->common_prefix && !keymatch(key0, &mode->bindings->key[0])) + continue; + 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; + } + if (mode->unknown && !mode->unknown(key0, key1)) + break; + } + return NULL; } -static void vis_prompt_clear(Prompt *prompt) { - Text *text = prompt->win->text; - while (text_undo(text)); +static Key getkey(void) { + Key key = { .str = "\0\0\0\0\0\0", .code = 0 }; + int keycode = getch(), len = 0; + if (keycode == ERR) + return key; + + if (keycode >= KEY_MIN) { + key.code = keycode; + } else { + char keychar = keycode; + key.str[len++] = keychar; + + 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); + } + } + + return key; } -void vis_prompt_hide(Vis *vis) { - Prompt *prompt = vis->prompt; - if (!prompt->active) - return; - prompt->active = false; - vis->win = prompt->editor; - prompt->editor = NULL; - vis->height++; - vis_prompt_clear(prompt); - vis_draw(vis); -} - -void vis_prompt_set(Vis *vis, const char *line) { - Text *text = vis->prompt->win->text; - vis_prompt_clear(vis->prompt); - text_insert_raw(text, 0, line, strlen(line)); - vis_window_draw(vis->prompt->win); -} - -char *vis_prompt_get(Vis *vis) { - Text *text = vis->prompt->win->text; - char *buf = malloc(text_size(text) + 1); - if (!buf) - return NULL; - size_t len = text_bytes_get(text, 0, text_size(text), buf); - buf[len] = '\0'; - return buf; +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_prev = mode = config->mode; + setup(); + + if (!(vis = editor_new(screen.w, screen.h))) + return 1; + if (!editor_syntax_load(vis, syntaxes, colors)) + return 1; + editor_statusbar_set(vis, config->statusbar); + + if (!editor_window_new(vis, argc > 1 ? argv[1] : NULL)) + return 1; + for (int i = 2; i < argc; i++) { + if (!editor_window_new(vis, argv[i])) + return 1; + } + + struct timeval idle = { .tv_usec = 0 }, *timeout = NULL; + Key key, key_prev, *key_mod = NULL; + + while (vis->running) { + if (screen.need_resize) { + resize_screen(&screen); + editor_resize(vis, screen.w, screen.h); + } + + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + editor_update(vis); + 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)) { + if (mode->idle) + mode->idle(); + timeout = NULL; + continue; + } + + key = getkey(); + 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 (key.code) + continue; + + if (mode->input) + mode->input(key.str, strlen(key.str)); + if (mode->idle) + timeout = &idle; + } + + editor_free(vis); + cleanup(); + return 0; } diff --git a/vis.h b/vis.h deleted file mode 100644 index ff12875..0000000 --- a/vis.h +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef VIS_H -#define VIS_H - -#include -#include -#include "text-motions.h" -#include "text-objects.h" -#include "window.h" -#include "register.h" - -typedef struct Vis Vis; -typedef struct VisWin VisWin; - -struct VisWin { - Vis *vis; /* editor instance to which this window belongs */ - Text *text; /* underlying text management */ - Win *win; /* vis window for the text area */ - WINDOW *statuswin; /* curses window for the statusbar */ - int width, height; /* window size including the statusbar */ - VisWin *prev, *next; /* neighbouring windows */ -}; - -typedef struct { - VisWin *win; - VisWin *editor; /* active editor window before prompt is shown */ - char *title; - WINDOW *titlewin; - bool active; -} Prompt; - -typedef void (*vis_statusbar_t)(WINDOW *win, bool active, const char *filename, size_t line, size_t col); - -enum Reg { - REG_a, - REG_b, - REG_c, - REG_d, - REG_e, - REG_f, - REG_g, - REG_h, - REG_i, - REG_j, - REG_k, - REG_l, - REG_m, - REG_n, - REG_o, - REG_p, - REG_q, - REG_r, - REG_s, - REG_t, - REG_u, - REG_v, - REG_w, - REG_x, - REG_y, - REG_z, - REG_DEFAULT, - REG_LAST, -}; - -enum Mark { - MARK_a, - MARK_b, - MARK_c, - MARK_d, - MARK_e, - MARK_f, - MARK_g, - MARK_h, - MARK_i, - MARK_j, - MARK_k, - MARK_l, - MARK_m, - MARK_n, - MARK_o, - MARK_p, - MARK_q, - MARK_r, - MARK_s, - MARK_t, - MARK_u, - MARK_v, - MARK_w, - MARK_x, - MARK_y, - MARK_z, -}; - -struct Vis { - int width, height; /* terminal size, available for all windows */ - VisWin *windows; /* list of windows */ - VisWin *win; /* currently active window */ - Syntax *syntaxes; /* NULL terminated array of syntax definitions */ - Register registers[REG_LAST]; - Prompt *prompt; - Regex *search_pattern; - void (*windows_arrange)(Vis*); /* current layout which places the windows */ - vis_statusbar_t statusbar; /* configurable user hook to draw statusbar */ - bool running; -}; - - -typedef struct { - short fg, bg; /* fore and background color */ - int attr; /* curses attributes */ -} Color; - -typedef struct { - char *rule; /* regex to search for */ - int cflags; /* compilation flags (REG_*) used when compiling */ - Color color; /* settings to apply in case of a match */ - regex_t regex; /* compiled form of the above rule */ -} SyntaxRule; - -#define SYNTAX_REGEX_RULES 10 - -typedef struct Syntax Syntax; - -struct Syntax { /* a syntax definition */ - char *name; /* syntax name */ - char *file; /* apply to files matching this regex */ - regex_t file_regex; /* compiled file name regex */ - SyntaxRule rules[SYNTAX_REGEX_RULES]; /* all rules for this file type */ -}; - -Vis *vis_new(int width, int height); -void vis_free(Vis*); -void vis_resize(Vis*, int width, int height); -void vis_draw(Vis*); -void vis_update(Vis*); -void vis_insert_key(Vis*, const char *c, size_t len); -void vis_replace_key(Vis*, const char *c, size_t len); -void vis_backspace_key(Vis*); -void vis_delete_key(Vis*); -void vis_insert(Vis*, size_t pos, const char *data, size_t len); -void vis_delete(Vis*, size_t pos, size_t len); - -// TODO comment -bool vis_syntax_load(Vis*, Syntax *syntaxes, Color *colors); -void vis_syntax_unload(Vis*); - -bool vis_window_new(Vis*, const char *filename); -void vis_window_close(Vis *vis); -void vis_window_split(Vis*, const char *filename); -void vis_window_vsplit(Vis*, const char *filename); -void vis_window_next(Vis*); -void vis_window_prev(Vis*); - -char *vis_prompt_get(Vis *vis); -void vis_prompt_set(Vis *vis, const char *line); -void vis_prompt_show(Vis *vis, const char *title); -void vis_prompt_hide(Vis *vis); - - -void vis_statusbar_set(Vis*, vis_statusbar_t); - -/* library initialization code, should be run at startup */ -void vis_init(void); -short vis_color_reserve(short fg, short bg); -short vis_color_get(short fg, short bg); - -#endif diff --git a/window.c b/window.c index 9e54c46..fa43492 100644 --- a/window.c +++ b/window.c @@ -19,7 +19,7 @@ #include #include #include -#include "vis.h" +#include "editor.h" #include "window.h" #include "text.h" #include "text-motions.h" diff --git a/window.h b/window.h index 0b0f933..439afa3 100644 --- a/window.h +++ b/window.h @@ -1,5 +1,5 @@ -#ifndef EDITOR_H -#define EDITOR_H +#ifndef WINDOW_H +#define WINDOW_H #include #include "text.h" -- cgit v1.2.3