diff options
Diffstat (limited to 'vis.c')
| -rw-r--r-- | vis.c | 1301 |
1 files changed, 910 insertions, 391 deletions
@@ -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; } |
