aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc André Tanner <mat@brain-dump.org>2014-09-09 21:21:30 +0200
committerMarc André Tanner <mat@brain-dump.org>2014-09-09 21:21:30 +0200
commit78e0601e4ee68177ef597e75d08da786b0cabd3d (patch)
tree51243a7e083e7d17d825ca50c448327d9cbf547b
parent98646851ade5386e3f72bfda4d94db20b65fde78 (diff)
downloadvis-78e0601e4ee68177ef597e75d08da786b0cabd3d.tar.gz
vis-78e0601e4ee68177ef597e75d08da786b0cabd3d.tar.xz
Rename vis.[ch] to editor.[ch] and main.c to vis.c
-rw-r--r--Makefile4
-rw-r--r--colors.c12
-rw-r--r--config.def.h24
-rw-r--r--editor.c483
-rw-r--r--editor.h166
-rw-r--r--main.c1002
-rw-r--r--util.h5
-rw-r--r--vis.c1301
-rw-r--r--vis.h166
-rw-r--r--window.c2
-rw-r--r--window.h4
11 files changed, 1587 insertions, 1582 deletions
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 <stdlib.h>
#include <curses.h>
-#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 <stdlib.h>
+#include <string.h>
+#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 <stddef.h>
+#include <regex.h>
+#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 <locale.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <signal.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/select.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-
-#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 <locale.h>
#include <stdlib.h>
+#include <unistd.h>
#include <string.h>
-#include "vis.h"
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#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 <stddef.h>
-#include <regex.h>
-#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 <wchar.h>
#include <ctype.h>
#include <errno.h>
-#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 <curses.h>
#include "text.h"