aboutsummaryrefslogtreecommitdiff
path: root/vis.c
diff options
context:
space:
mode:
Diffstat (limited to 'vis.c')
-rw-r--r--vis.c1301
1 files changed, 910 insertions, 391 deletions
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;
}