diff options
| -rw-r--r-- | config.def.h | 14 | ||||
| -rw-r--r-- | editor.c | 453 | ||||
| -rw-r--r-- | editor.h | 339 | ||||
| -rw-r--r-- | ui-curses.c | 8 | ||||
| -rw-r--r-- | ui.h | 4 | ||||
| -rw-r--r-- | view.c | 2 | ||||
| -rw-r--r-- | vis.c | 690 | ||||
| -rw-r--r-- | vis.h | 240 |
8 files changed, 837 insertions, 913 deletions
diff --git a/config.def.h b/config.def.h index 9d049fb..722dc07 100644 --- a/config.def.h +++ b/config.def.h @@ -534,7 +534,7 @@ static KeyAction vis_action[] = { [VIS_ACTION_REDRAW] = { "editor-redraw", "Redraw current editor content", - call, { .f = editor_draw } + call, { .f = vis_draw } }, [VIS_ACTION_REPLACE_CHAR] = { "replace-char", @@ -654,12 +654,12 @@ static KeyAction vis_action[] = { [VIS_ACTION_WINDOW_NEXT] = { "window-next", "Focus next window", - call, { .f = editor_window_next } + call, { .f = vis_window_next } }, [VIS_ACTION_WINDOW_PREV] = { "window-prev", "Focus previous window", - call, { .f = editor_window_prev } + call, { .f = vis_window_prev } }, [VIS_ACTION_OPEN_LINE_ABOVE] = { "open-line-above", @@ -1279,7 +1279,7 @@ static KeyBinding vis_mode_prompt[] = { }; static void vis_mode_prompt_input(Vis *vis, const char *str, size_t len) { - editor_insert_key(vis, str, len); + vis_insert_key(vis, str, len); } static void vis_mode_prompt_enter(Vis *vis, Mode *old) { @@ -1289,7 +1289,7 @@ static void vis_mode_prompt_enter(Vis *vis, Mode *old) { static void vis_mode_prompt_leave(Vis *vis, Mode *new) { if (new->isuser) - editor_prompt_hide(vis); + vis_prompt_hide(vis); } static KeyBinding vis_mode_insert[] = { @@ -1328,7 +1328,7 @@ static void vis_mode_insert_input(Vis *vis, const char *str, size_t len) { oldpos = pos + len; action_reset(vis, &vis->action_prev); vis->action_prev.op = &ops[OP_REPEAT_INSERT]; - editor_insert_key(vis, str, len); + vis_insert_key(vis, str, len); } static KeyBinding vis_mode_replace[] = { @@ -1349,7 +1349,7 @@ static void vis_mode_replace_input(Vis *vis, const char *str, size_t len) { oldpos = pos + len; action_reset(vis, &vis->action_prev); vis->action_prev.op = &ops[OP_REPEAT_REPLACE]; - editor_replace_key(vis, str, len); + vis_replace_key(vis, str, len); } /* diff --git a/editor.c b/editor.c deleted file mode 100644 index 8a17805..0000000 --- a/editor.c +++ /dev/null @@ -1,453 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <stdarg.h> -#include <unistd.h> -#include <errno.h> -#include "editor.h" -#include "util.h" -#include "text-motions.h" -#include "text-util.h" - -static void file_free(Editor *ed, File *file); -static File *file_new(Editor *ed, const char *filename); -static Win *window_new_file(Editor *ed, File *file); -static void window_free(Win *win); -static void editor_windows_invalidate(Editor *ed, size_t start, size_t end); - -static void window_selection_changed(void *win, Filerange *sel) { - File *file = ((Win*)win)->file; - if (text_range_valid(sel)) { - file->marks[MARK_SELECTION_START] = text_mark_set(file->text, sel->start); - file->marks[MARK_SELECTION_END] = text_mark_set(file->text, sel->end); - } -} - -void editor_window_name(Win *win, const char *filename) { - File *file = win->file; - free((char*)file->name); - file->name = filename ? strdup(filename) : NULL; - - if (filename) { - for (Syntax *syn = win->editor->syntaxes; syn && syn->name; syn++) { - if (!regexec(&syn->file_regex, filename, 0, NULL, 0)) { - view_syntax_set(win->view, syn); - break; - } - } - } -} - -void editor_windows_arrange(Editor *ed, enum UiLayout layout) { - ed->ui->arrange(ed->ui, layout); -} - -bool editor_window_reload(Win *win) { - const char *name = win->file->name; - if (!name) - return false; /* can't reload unsaved file */ - /* temporarily unset file name, otherwise file_new returns the same File */ - win->file->name = NULL; - File *file = file_new(win->editor, name); - win->file->name = name; - if (!file) - return false; - file_free(win->editor, win->file); - win->file = file; - win->ui->reload(win->ui, file); - return true; -} - -bool editor_window_split(Win *original) { - Win *win = window_new_file(original->editor, original->file); - if (!win) - return false; - win->file = original->file; - win->file->refcount++; - view_syntax_set(win->view, view_syntax_get(original->view)); - view_options_set(win->view, view_options_get(original->view)); - view_cursor_to(win->view, view_cursor_get(original->view)); - editor_draw(win->editor); - return true; -} - -void editor_resize(Editor *ed) { - ed->ui->resize(ed->ui); -} - -void editor_window_next(Editor *ed) { - Win *sel = ed->win; - if (!sel) - return; - ed->win = ed->win->next; - if (!ed->win) - ed->win = ed->windows; - ed->ui->window_focus(ed->win->ui); -} - -void editor_window_prev(Editor *ed) { - Win *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); - ed->ui->window_focus(ed->win->ui); -} - -static void editor_windows_invalidate(Editor *ed, size_t start, size_t end) { - for (Win *win = ed->windows; win; win = win->next) { - if (ed->win != win && ed->win->file == win->file) { - Filerange view = view_viewport_get(win->view); - if ((view.start <= start && start <= view.end) || - (view.start <= end && end <= view.end)) - view_draw(win->view); - } - } - view_draw(ed->win->view); -} - -int editor_tabwidth_get(Editor *ed) { - return ed->tabwidth; -} - -void editor_tabwidth_set(Editor *ed, int tabwidth) { - if (tabwidth < 1 || tabwidth > 8) - return; - for (Win *win = ed->windows; win; win = win->next) - view_tabwidth_set(win->view, tabwidth); - ed->tabwidth = tabwidth; -} - -bool editor_syntax_load(Editor *ed, Syntax *syntaxes) { - 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; - for (int j = 0; j < LENGTH(syn->rules); j++) { - SyntaxRule *rule = &syn->rules[j]; - if (!rule->rule) - break; - int cflags = REG_EXTENDED; - if (!rule->multiline) - cflags |= REG_NEWLINE; - if (regcomp(&rule->regex, rule->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; -} - -void editor_draw(Editor *ed) { - ed->ui->draw(ed->ui); -} - -void editor_update(Editor *ed) { - ed->ui->update(ed->ui); -} - -void editor_suspend(Editor *ed) { - ed->ui->suspend(ed->ui); -} - -bool editor_mode_map(Mode *mode, const char *name, KeyBinding *binding) { - return map_put(mode->bindings, name, binding); -} - -bool editor_mode_bindings(Mode *mode, KeyBinding **bindings) { - if (!mode->bindings) - mode->bindings = map_new(); - if (!mode->bindings) - return false; - bool success = true; - for (KeyBinding *kb = *bindings; kb->key; kb++) { - if (!editor_mode_map(mode, kb->key, kb)) - success = false; - } - return success; -} - -bool editor_mode_unmap(Mode *mode, const char *name) { - return map_delete(mode->bindings, name); -} - -bool editor_action_register(Editor *ed, KeyAction *action) { - if (!ed->actions) - ed->actions = map_new(); - if (!ed->actions) - return false; - return map_put(ed->actions, action->name, action); -} - -static void window_free(Win *win) { - if (!win) - return; - Editor *ed = win->editor; - if (ed && ed->ui) - ed->ui->window_free(win->ui); - view_free(win->view); - ringbuf_free(win->jumplist); - free(win); -} - -static Win *window_new_file(Editor *ed, File *file) { - Win *win = calloc(1, sizeof(Win)); - if (!win) - return NULL; - win->editor = ed; - win->file = file; - win->events = (ViewEvent) { - .data = win, - .selection = window_selection_changed, - }; - win->jumplist = ringbuf_alloc(31); - win->view = view_new(file->text, &win->events); - win->ui = ed->ui->window_new(ed->ui, win->view, file); - if (!win->jumplist || !win->view || !win->ui) { - window_free(win); - return NULL; - } - view_tabwidth_set(win->view, ed->tabwidth); - if (ed->windows) - ed->windows->prev = win; - win->next = ed->windows; - ed->windows = win; - ed->win = win; - ed->ui->window_focus(win->ui); - return win; -} - -static void file_free(Editor *ed, File *file) { - if (!file) - return; - if (--file->refcount > 0) - return; - - text_free(file->text); - free((char*)file->name); - - if (file->prev) - file->prev->next = file->next; - if (file->next) - file->next->prev = file->prev; - if (ed->files == file) - ed->files = file->next; - free(file); -} - -static File *file_new_text(Editor *ed, Text *text) { - File *file = calloc(1, sizeof(*file)); - if (!file) - return NULL; - file->text = text; - file->stat = text_stat(text); - file->refcount++; - if (ed->files) - ed->files->prev = file; - file->next = ed->files; - ed->files = file; - return file; -} - -static File *file_new(Editor *ed, const char *filename) { - if (filename) { - /* try to detect whether the same file is already open in another window - * TODO: do this based on inodes */ - for (File *file = ed->files; file; file = file->next) { - if (file->name && strcmp(file->name, filename) == 0) { - file->refcount++; - return file; - } - } - } - - Text *text = text_load(filename); - if (!text && filename && errno == ENOENT) - text = text_load(NULL); - if (!text) - return NULL; - - File *file = file_new_text(ed, text); - if (!file) { - text_free(text); - return NULL; - } - - if (filename) - file->name = strdup(filename); - return file; -} - -bool editor_window_new(Editor *ed, const char *filename) { - File *file = file_new(ed, filename); - if (!file) - return false; - Win *win = window_new_file(ed, file); - if (!win) { - file_free(ed, file); - return false; - } - - editor_window_name(win, filename); - editor_draw(ed); - - return true; -} - -void editor_window_close(Win *win) { - Editor *ed = win->editor; - file_free(ed, win->file); - if (win->prev) - win->prev->next = win->next; - if (win->next) - win->next->prev = win->prev; - if (ed->windows == win) - ed->windows = win->next; - if (ed->win == win) - ed->win = win->next ? win->next : win->prev; - if (ed->prompt_window == win) - ed->prompt_window = NULL; - window_free(win); - if (ed->win) - ed->ui->window_focus(ed->win->ui); - editor_draw(ed); -} - -Editor *editor_new(Ui *ui) { - if (!ui) - return NULL; - Editor *ed = calloc(1, sizeof(Editor)); - if (!ed) - return NULL; - ed->ui = ui; - ed->ui->init(ed->ui, ed); - ed->tabwidth = 8; - ed->expandtab = false; - if (!(ed->prompt = calloc(1, sizeof(Win)))) - goto err; - if (!(ed->prompt->file = calloc(1, sizeof(File)))) - goto err; - if (!(ed->prompt->file->text = text_load(NULL))) - goto err; - if (!(ed->prompt->view = view_new(ed->prompt->file->text, NULL))) - goto err; - if (!(ed->prompt->ui = ed->ui->prompt_new(ed->ui, ed->prompt->view, ed->prompt->file))) - goto err; - if (!(ed->search_pattern = text_regex_new())) - goto err; - return ed; -err: - editor_free(ed); - return NULL; -} - -void editor_free(Editor *ed) { - if (!ed) - return; - while (ed->windows) - editor_window_close(ed->windows); - file_free(ed, ed->prompt->file); - window_free(ed->prompt); - text_regex_free(ed->search_pattern); - for (int i = 0; i < REG_LAST; i++) - register_release(&ed->registers[i]); - for (int i = 0; i < MACRO_LAST; i++) - macro_release(&ed->macros[i]); - editor_syntax_unload(ed); - ed->ui->free(ed->ui); - map_free(ed->cmds); - map_free(ed->options); - map_free(ed->actions); - buffer_release(&ed->buffer_repeat); - free(ed); -} - -void editor_insert(Editor *ed, size_t pos, const char *data, size_t len) { - text_insert(ed->win->file->text, pos, data, len); - editor_windows_invalidate(ed, pos, pos + len); -} - -void editor_insert_key(Editor *ed, const char *data, size_t len) { - for (Cursor *c = view_cursors(ed->win->view); c; c = view_cursors_next(c)) { - size_t pos = view_cursors_pos(c); - editor_insert(ed, pos, data, len); - view_cursors_scroll_to(c, pos + len); - } -} - -void editor_replace(Editor *ed, size_t pos, const char *data, size_t len) { - size_t chars = 0; - for (size_t i = 0; i < len; i++) { - if (ISUTF8(data[i])) - chars++; - } - - Text *txt = ed->win->file->text; - Iterator it = text_iterator_get(txt, pos); - for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n'; ) - text_iterator_char_next(&it, NULL); - - text_delete(txt, pos, it.pos - pos); - editor_insert(ed, pos, data, len); -} - -void editor_replace_key(Editor *ed, const char *data, size_t len) { - for (Cursor *c = view_cursors(ed->win->view); c; c = view_cursors_next(c)) { - size_t pos = view_cursors_pos(c); - editor_replace(ed, pos, data, len); - view_cursors_scroll_to(c, pos + len); - } -} - -void editor_delete(Editor *ed, size_t pos, size_t len) { - text_delete(ed->win->file->text, pos, len); - editor_windows_invalidate(ed, pos, pos + len); -} - -void editor_prompt_show(Editor *ed, const char *title, const char *text) { - if (ed->prompt_window) - return; - ed->prompt_window = ed->win; - ed->win = ed->prompt; - ed->prompt_type = title[0]; - ed->ui->prompt(ed->ui, title, text); -} - -void editor_prompt_hide(Editor *ed) { - if (!ed->prompt_window) - return; - ed->ui->prompt_hide(ed->ui); - ed->win = ed->prompt_window; - ed->prompt_window = NULL; -} - -char *editor_prompt_get(Editor *ed) { - return ed->ui->prompt_input(ed->ui); -} - -void editor_info_show(Editor *ed, const char *msg, ...) { - va_list ap; - va_start(ap, msg); - ed->ui->info(ed->ui, msg, ap); - va_end(ap); -} - -void editor_info_hide(Editor *ed) { - ed->ui->info_hide(ed->ui); -} diff --git a/editor.h b/editor.h deleted file mode 100644 index abb0b57..0000000 --- a/editor.h +++ /dev/null @@ -1,339 +0,0 @@ -#ifndef EDITOR_H -#define EDITOR_H - -#include <signal.h> -#include <stddef.h> -#include <stdbool.h> -#include <setjmp.h> - -typedef struct Editor Editor; -typedef Editor Vis; -typedef struct File File; -typedef struct Win Win; - -#include "ui.h" -#include "view.h" -#include "register.h" -#include "macro.h" -#include "syntax.h" -#include "ring-buffer.h" -#include "map.h" -#include "text-regex.h" - -typedef union { - bool b; - int i; - const char *s; - void (*w)(View*); /* generic window commands */ - void (*f)(Editor*); /* generic editor commands */ -} Arg; - -typedef struct { - const char *name; - const char *help; - const char* (*func)(Vis*, const char *keys, const Arg*); - /* returns a pointer to the first not consumed character in keys - * or NULL if not enough input was available to complete the command */ - const Arg arg; - -} KeyAction; - -typedef struct { - const char *key; - KeyAction *action; - const char *alias; -} KeyBinding; - -/* a mode contains a set of key bindings which are currently valid. - * - * each mode can specify one parent mode which is consultated if a given key - * is not found in the current mode. hence the modes form a tree which is - * searched from the current mode up towards the root mode until a valid binding - * is found. - * - * if no binding is found, mode->input(...) is called and the user entered - * keys are passed as argument. this is used to change the document content. - */ -typedef struct Mode Mode; -struct Mode { - Mode *parent; /* if no match is found in this mode, search will continue there */ - Map *bindings; - KeyBinding *default_bindings; - const char *name; /* descriptive, user facing name of the mode */ - const char *status; /* name displayed in the window status bar */ - const char *help; /* short description used by :help */ - bool isuser; /* whether this is a user or internal mode */ - void (*enter)(Vis*, Mode *old); /* called right before the mode becomes active */ - void (*leave)(Vis*, Mode *new); /* called right before the mode becomes inactive */ - void (*input)(Vis*, const char*, size_t); /* called whenever a key is not found in this mode and all its parent modes */ - void (*idle)(Vis*); /* called whenever a certain idle time i.e. without any user input elapsed */ - time_t idle_timeout; /* idle time in seconds after which the registered function will be called */ - bool visual; /* whether text selection is possible in this mode */ -}; - -typedef struct { - int count; /* how many times should the command be executed? */ - Register *reg; /* always non-NULL, set to a default register */ - Filerange range; /* which part of the file should be affected by the operator */ - size_t pos; /* at which byte from the start of the file should the operation start? */ - bool linewise; /* should the changes always affect whole lines? */ - const Arg *arg; /* arbitrary arguments */ -} OperatorContext; - -typedef struct { - size_t (*func)(Vis*, Text*, OperatorContext*); /* operator logic, returns new cursor position */ -} Operator; - -typedef struct { - /* TODO: merge types / use union to save space */ - size_t (*cur)(Cursor*); /* a movement based on current window content from view.h */ - size_t (*txt)(Text*, size_t pos); /* a movement form text-motions.h */ - size_t (*file)(Vis*, File*, size_t pos); - size_t (*vis)(Vis*, Text*, size_t pos); - size_t (*view)(Vis*, View*); - size_t (*win)(Vis*, Win*, size_t pos); - enum { - LINEWISE = 1 << 0, - CHARWISE = 1 << 1, - INCLUSIVE = 1 << 2, - EXCLUSIVE = 1 << 3, - IDEMPOTENT = 1 << 4, - JUMP = 1 << 5, - } type; - int count; -} Movement; - -typedef struct { - Filerange (*range)(Text*, size_t pos); /* a text object from text-objects.h */ - enum { - INNER, - OUTER, - } type; -} TextObject; - -typedef struct { /** collects all information until an operator is executed */ - int count; - int type; - const Operator *op; - const Movement *movement; - const TextObject *textobj; - Register *reg; - int mark; - Arg arg; -} Action; - -enum CmdOpt { /* option flags for command definitions */ - CMD_OPT_NONE, /* no option (default value) */ - CMD_OPT_FORCE, /* whether the command can be forced by appending '!' */ - CMD_OPT_ARGS, /* whether the command line should be parsed in to space - * separated arguments to placed into argv, otherwise argv[1] - * will contain the remaining command line unmodified */ -}; - -typedef struct { /* command definitions for the ':'-prompt */ - const char *name[3]; /* name and optional alias for the command */ - /* command logic called with a NULL terminated array of arguments. - * argv[0] will be the command name */ - bool (*cmd)(Vis*, Filerange*, enum CmdOpt opt, const char *argv[]); - enum CmdOpt opt; /* command option flags */ -} Command; - -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, - MARK_SELECTION_START, - MARK_SELECTION_END, - MARK_INVALID, -}; - -struct File { - Text *text; - const char *name; - volatile sig_atomic_t truncated; - bool is_stdin; - struct stat stat; - int refcount; - Mark marks[MARK_INVALID]; - File *next, *prev; -}; - -typedef struct { - time_t state; /* state of the text, used to invalidate change list */ - size_t index; /* #number of changes */ - size_t pos; /* where the current change occured */ -} ChangeList; - -struct Win { - Editor *editor; /* editor instance to which this window belongs */ - UiWin *ui; - File *file; /* file being displayed in this window */ - View *view; /* currently displayed part of underlying text */ - ViewEvent events; - RingBuffer *jumplist; /* LRU jump management */ - ChangeList changelist; /* state for iterating through least recently changes */ - Win *prev, *next; /* neighbouring windows */ -}; - -#define MACRO_LAST 26 - -struct Editor { - Ui *ui; - File *files; - Win *windows; /* list of windows */ - Win *win; /* currently active window */ - Syntax *syntaxes; /* NULL terminated array of syntax definitions */ - Register registers[REG_LAST]; /* register used for copy and paste */ - Macro macros[MACRO_LAST]; /* recorded macros */ - Macro *recording, *last_recording;/* currently and least recently recorded macro */ - Win *prompt; /* 1-line height window to get user input */ - Win *prompt_window; /* window which was focused before prompt was shown */ - char prompt_type; /* command ':' or search '/','?' prompt */ - Regex *search_pattern; /* last used search pattern */ - char search_char[8]; /* last used character to search for via 'f', 'F', 't', 'T' */ - int last_totill; /* last to/till movement used for ';' and ',' */ - int tabwidth; /* how many spaces should be used to display a tab */ - bool expandtab; /* whether typed tabs should be converted to spaces */ - bool autoindent; /* whether indentation should be copied from previous line on newline */ - Map *cmds; /* ":"-commands, used for unique prefix queries */ - Map *options; /* ":set"-options */ - Buffer buffer_repeat; /* holds data to repeat last insertion/replacement */ - Buffer input_queue; /* holds pending input keys */ - - Action action; /* current action which is in progress */ - Action action_prev; /* last operator action used by the repeat '.' key */ - Mode *mode; /* currently active mode, used to search for keybindings */ - Mode *mode_prev; /* previsouly active user mode */ - Mode *mode_before_prompt; /* user mode which was active before entering prompt */ - volatile bool running; /* exit main loop once this becomes false */ - volatile sig_atomic_t cancel_filter; /* abort external command */ - volatile sig_atomic_t sigbus; - sigjmp_buf sigbus_jmpbuf; - Map *actions; /* built in special editor keys / commands */ -}; - -Editor *editor_new(Ui*); -void editor_free(Editor*); -void editor_resize(Editor*); -void editor_draw(Editor*); -void editor_update(Editor*); -void editor_suspend(Editor*); - -bool editor_mode_bindings(Mode*, KeyBinding**); -bool editor_mode_map(Mode*, const char *name, KeyBinding*); -bool editor_mode_unmap(Mode*, const char *name); - -bool editor_action_register(Editor*, KeyAction*); - -/* these function operate on the currently focused window but make sure - * that all windows which show the affected region are redrawn too. */ -void editor_insert_key(Editor*, const char *data, size_t len); -void editor_replace_key(Editor*, const char *data, size_t len); -void editor_insert(Editor*, size_t pos, const char *data, size_t len); -void editor_delete(Editor*, size_t pos, size_t len); -void editor_replace(Editor*, size_t pos, const char *data, size_t len); - -/* set tabwidth (must be in range [1, 8], affects all windows */ -void editor_tabwidth_set(Editor*, int tabwidth); -int editor_tabwidth_get(Editor*); - -/* load a set of syntax highlighting definitions which will be associated - * to the underlying window based on the file type loaded. - * - * The parameter `syntaxes' has to point to a NULL terminated array. - */ -bool editor_syntax_load(Editor*, Syntax *syntaxes); -void editor_syntax_unload(Editor*); - -/* creates a new window, and loads the given file. if filename is NULL - * an unamed / empty buffer is created. If the given file is already opened - * in another window, share the underlying text that is changes will be - * visible in both windows */ -bool editor_window_new(Editor*, const char *filename); -/* reload the file currently displayed in the window from disk */ -bool editor_window_reload(Win*); -void editor_window_close(Win*); -/* split the given window. changes to the displayed text will be reflected - * in both windows */ -bool editor_window_split(Win*); -/* focus the next / previous window */ -void editor_window_next(Editor*); -void editor_window_prev(Editor*); -/* set the filename of the file displayed in this window */ -void editor_window_name(Win*, const char *filename); - -/* rearrange all windows either vertically or horizontally */ -void editor_windows_arrange(Editor*, enum UiLayout); -/* display a user prompt with a certain title and default text */ -void editor_prompt_show(Editor*, const char *title, const char *text); -/* hide the user prompt if it is currently shown */ -void editor_prompt_hide(Editor*); -/* return the content of the command prompt in a malloc(3)-ed string - * which the call site has to free. */ -char *editor_prompt_get(Editor*); -/* replace the current command line content with the one given */ -void editor_prompt_set(Editor*, const char *line); - -/* display a message to the user */ -void editor_info_show(Editor*, const char *msg, ...); -void editor_info_hide(Editor*); - -/* look up a curses color pair for the given combination of fore and - * background color */ -short editor_color_get(short fg, short bg); - -#endif diff --git a/ui-curses.c b/ui-curses.c index 9e67e92..1e3c925 100644 --- a/ui-curses.c +++ b/ui-curses.c @@ -63,7 +63,7 @@ typedef struct UiCursesWin UiCursesWin; typedef struct { Ui ui; /* generic ui interface, has to be the first struct member */ - Editor *ed; /* editor instance to which this ui belongs */ + Vis *vis; /* editor instance to which this ui belongs */ UiCursesWin *windows; /* all windows managed by this ui */ UiCursesWin *selwin; /* the currently selected layout */ char prompt_title[255]; /* prompt_title[0] == '\0' if prompt isn't shown */ @@ -606,7 +606,7 @@ static void ui_window_draw_status(UiWin *w) { if (!win->winstatus) return; UiCurses *uic = win->ui; - Editor *vis = uic->ed; + Vis *vis = uic->vis; bool focused = uic->selwin == win; const char *filename = win->file->name; CursorPos pos = view_cursor_getpos(win->view); @@ -925,9 +925,9 @@ static void ui_prompt_hide(Ui *ui) { ui_resize_to(ui, uic->width, uic->height); } -static bool ui_init(Ui *ui, Editor *ed) { +static bool ui_init(Ui *ui, Vis *vis) { UiCurses *uic = (UiCurses*)ui; - uic->ed = ed; + uic->vis = vis; return true; } @@ -27,10 +27,10 @@ enum UiOption { #include "text.h" #include "view.h" -#include "editor.h" +#include "vis.h" struct Ui { - bool (*init)(Ui*, Editor*); + bool (*init)(Ui*, Vis*); void (*free)(Ui*); void (*resize)(Ui*); UiWin* (*window_new)(Ui*, View*, File*); @@ -19,7 +19,7 @@ #include <ctype.h> #include <errno.h> #include <regex.h> -#include "editor.h" +#include "vis.h" #include "view.h" #include "syntax.h" #include "text.h" @@ -42,6 +42,478 @@ #include "map.h" #include "libutf.h" +typedef struct { + int count; /* how many times should the command be executed? */ + Register *reg; /* always non-NULL, set to a default register */ + Filerange range; /* which part of the file should be affected by the operator */ + size_t pos; /* at which byte from the start of the file should the operation start? */ + bool linewise; /* should the changes always affect whole lines? */ + const Arg *arg; /* arbitrary arguments */ +} OperatorContext; + +struct Operator { + size_t (*func)(Vis*, Text*, OperatorContext*); /* operator logic, returns new cursor position */ +}; + +struct Movement { + /* TODO: merge types / use union to save space */ + size_t (*cur)(Cursor*); /* a movement based on current window content from view.h */ + size_t (*txt)(Text*, size_t pos); /* a movement form text-motions.h */ + size_t (*file)(Vis*, File*, size_t pos); + size_t (*vis)(Vis*, Text*, size_t pos); + size_t (*view)(Vis*, View*); + size_t (*win)(Vis*, Win*, size_t pos); + enum { + LINEWISE = 1 << 0, + CHARWISE = 1 << 1, + INCLUSIVE = 1 << 2, + EXCLUSIVE = 1 << 3, + IDEMPOTENT = 1 << 4, + JUMP = 1 << 5, + } type; + int count; +}; + +struct TextObject { + Filerange (*range)(Text*, size_t pos); /* a text object from text-objects.h */ + enum { + INNER, + OUTER, + } type; +}; + +enum CmdOpt { /* option flags for command definitions */ + CMD_OPT_NONE, /* no option (default value) */ + CMD_OPT_FORCE, /* whether the command can be forced by appending '!' */ + CMD_OPT_ARGS, /* whether the command line should be parsed in to space + * separated arguments to placed into argv, otherwise argv[1] + * will contain the remaining command line unmodified */ +}; + +typedef struct { /* command definitions for the ':'-prompt */ + const char *name[3]; /* name and optional alias for the command */ + /* command logic called with a NULL terminated array of arguments. + * argv[0] will be the command name */ + bool (*cmd)(Vis*, Filerange*, enum CmdOpt opt, const char *argv[]); + enum CmdOpt opt; /* command option flags */ +} Command; + +/** window / file handling */ + +static void file_free(Vis *vis, File *file) { + if (!file) + return; + if (--file->refcount > 0) + return; + + text_free(file->text); + free((char*)file->name); + + if (file->prev) + file->prev->next = file->next; + if (file->next) + file->next->prev = file->prev; + if (vis->files == file) + vis->files = file->next; + free(file); +} + +static File *file_new_text(Vis *vis, Text *text) { + File *file = calloc(1, sizeof(*file)); + if (!file) + return NULL; + file->text = text; + file->stat = text_stat(text); + file->refcount++; + if (vis->files) + vis->files->prev = file; + file->next = vis->files; + vis->files = file; + return file; +} + +static File *file_new(Vis *vis, const char *filename) { + if (filename) { + /* try to detect whether the same file is already open in another window + * TODO: do this based on inodes */ + for (File *file = vis->files; file; file = file->next) { + if (file->name && strcmp(file->name, filename) == 0) { + file->refcount++; + return file; + } + } + } + + Text *text = text_load(filename); + if (!text && filename && errno == ENOENT) + text = text_load(NULL); + if (!text) + return NULL; + + File *file = file_new_text(vis, text); + if (!file) { + text_free(text); + return NULL; + } + + if (filename) + file->name = strdup(filename); + return file; +} + +static void window_name(Win *win, const char *filename) { + File *file = win->file; + if (filename != file->name) { + free((char*)file->name); + file->name = filename ? strdup(filename) : NULL; + } + + if (filename) { + Vis *vis = win->editor; + for (Syntax *syn = vis->syntaxes; syn && syn->name; syn++) { + if (!regexec(&syn->file_regex, filename, 0, NULL, 0)) { + view_syntax_set(win->view, syn); + for (const char **opt = syn->settings; opt && *opt; opt++) + vis_cmd(vis, *opt); + break; + } + } + } +} + +static void windows_invalidate(Vis *vis, size_t start, size_t end) { + for (Win *win = vis->windows; win; win = win->next) { + if (vis->win != win && vis->win->file == win->file) { + Filerange view = view_viewport_get(win->view); + if ((view.start <= start && start <= view.end) || + (view.start <= end && end <= view.end)) + view_draw(win->view); + } + } + view_draw(vis->win->view); +} + +static void window_selection_changed(void *win, Filerange *sel) { + File *file = ((Win*)win)->file; + if (text_range_valid(sel)) { + file->marks[MARK_SELECTION_START] = text_mark_set(file->text, sel->start); + file->marks[MARK_SELECTION_END] = text_mark_set(file->text, sel->end); + } +} + +static void windows_arrange(Vis *vis, enum UiLayout layout) { + vis->ui->arrange(vis->ui, layout); +} + +static void window_free(Win *win) { + if (!win) + return; + Vis *vis = win->editor; + if (vis && vis->ui) + vis->ui->window_free(win->ui); + view_free(win->view); + ringbuf_free(win->jumplist); + free(win); +} + +static Win *window_new_file(Vis *vis, File *file) { + Win *win = calloc(1, sizeof(Win)); + if (!win) + return NULL; + win->editor = vis; + win->file = file; + win->events = (ViewEvent) { + .data = win, + .selection = window_selection_changed, + }; + win->jumplist = ringbuf_alloc(31); + win->view = view_new(file->text, &win->events); + win->ui = vis->ui->window_new(vis->ui, win->view, file); + if (!win->jumplist || !win->view || !win->ui) { + window_free(win); + return NULL; + } + view_tabwidth_set(win->view, vis->tabwidth); + if (vis->windows) + vis->windows->prev = win; + win->next = vis->windows; + vis->windows = win; + vis->win = win; + vis->ui->window_focus(win->ui); + return win; +} + +bool vis_window_reload(Win *win) { + const char *name = win->file->name; + if (!name) + return false; /* can't reload unsaved file */ + /* temporarily unset file name, otherwise file_new returns the same File */ + win->file->name = NULL; + File *file = file_new(win->editor, name); + win->file->name = name; + if (!file) + return false; + file_free(win->editor, win->file); + win->file = file; + win->ui->reload(win->ui, file); + return true; +} + +bool vis_window_split(Win *original) { + Win *win = window_new_file(original->editor, original->file); + if (!win) + return false; + win->file = original->file; + win->file->refcount++; + view_syntax_set(win->view, view_syntax_get(original->view)); + view_options_set(win->view, view_options_get(original->view)); + view_cursor_to(win->view, view_cursor_get(original->view)); + vis_draw(win->editor); + return true; +} + +void vis_resize(Vis *vis) { + vis->ui->resize(vis->ui); +} + +void vis_window_next(Vis *vis) { + Win *sel = vis->win; + if (!sel) + return; + vis->win = vis->win->next; + if (!vis->win) + vis->win = vis->windows; + vis->ui->window_focus(vis->win->ui); +} + +void vis_window_prev(Vis *vis) { + Win *sel = vis->win; + if (!sel) + return; + vis->win = vis->win->prev; + if (!vis->win) + for (vis->win = vis->windows; vis->win->next; vis->win = vis->win->next); + vis->ui->window_focus(vis->win->ui); +} + +static int tabwidth_get(Vis *vis) { + return vis->tabwidth; +} + +static void tabwidth_set(Vis *vis, int tabwidth) { + if (tabwidth < 1 || tabwidth > 8) + return; + for (Win *win = vis->windows; win; win = win->next) + view_tabwidth_set(win->view, tabwidth); + vis->tabwidth = tabwidth; +} + +bool vis_syntax_load(Vis *vis, Syntax *syntaxes) { + 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; + for (int j = 0; j < LENGTH(syn->rules); j++) { + SyntaxRule *rule = &syn->rules[j]; + if (!rule->rule) + break; + int cflags = REG_EXTENDED; + if (!rule->multiline) + cflags |= REG_NEWLINE; + if (regcomp(&rule->regex, rule->rule, cflags)) + success = false; + } + } + + return success; +} + +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); + } + } + + vis->syntaxes = NULL; +} + +void vis_draw(Vis *vis) { + vis->ui->draw(vis->ui); +} + +void vis_update(Vis *vis) { + vis->ui->update(vis->ui); +} + +void vis_suspend(Vis *vis) { + vis->ui->suspend(vis->ui); +} + +bool vis_window_new(Vis *vis, const char *filename) { + File *file = file_new(vis, filename); + if (!file) + return false; + Win *win = window_new_file(vis, file); + if (!win) { + file_free(vis, file); + return false; + } + + window_name(win, filename); + vis_draw(vis); + + return true; +} + +void vis_window_close(Win *win) { + Vis *vis = win->editor; + file_free(vis, win->file); + if (win->prev) + win->prev->next = win->next; + if (win->next) + win->next->prev = win->prev; + if (vis->windows == win) + vis->windows = win->next; + if (vis->win == win) + vis->win = win->next ? win->next : win->prev; + if (vis->prompt_window == win) + vis->prompt_window = NULL; + window_free(win); + if (vis->win) + vis->ui->window_focus(vis->win->ui); + vis_draw(vis); +} + +Vis *vis_new0(Ui *ui) { + if (!ui) + return NULL; + Vis *vis = calloc(1, sizeof(Vis)); + if (!vis) + return NULL; + vis->ui = ui; + vis->ui->init(vis->ui, vis); + vis->tabwidth = 8; + vis->expandtab = false; + if (!(vis->prompt = calloc(1, sizeof(Win)))) + goto err; + if (!(vis->prompt->file = calloc(1, sizeof(File)))) + goto err; + if (!(vis->prompt->file->text = text_load(NULL))) + goto err; + if (!(vis->prompt->view = view_new(vis->prompt->file->text, NULL))) + goto err; + if (!(vis->prompt->ui = vis->ui->prompt_new(vis->ui, vis->prompt->view, vis->prompt->file))) + goto err; + if (!(vis->search_pattern = text_regex_new())) + goto err; + return vis; +err: + vis_free(vis); + return NULL; +} + +void vis_free(Vis *vis) { + if (!vis) + return; + while (vis->windows) + vis_window_close(vis->windows); + file_free(vis, vis->prompt->file); + window_free(vis->prompt); + text_regex_free(vis->search_pattern); + for (int i = 0; i < LENGTH(vis->registers); i++) + register_release(&vis->registers[i]); + for (int i = 0; i < LENGTH(vis->macros); i++) + macro_release(&vis->macros[i]); + vis_syntax_unload(vis); + vis->ui->free(vis->ui); + map_free(vis->cmds); + map_free(vis->options); + map_free(vis->actions); + buffer_release(&vis->buffer_repeat); + free(vis); +} + +void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) { + text_insert(vis->win->file->text, pos, data, len); + windows_invalidate(vis, pos, pos + len); +} + +void vis_insert_key(Vis *vis, const char *data, size_t len) { + for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) { + size_t pos = view_cursors_pos(c); + vis_insert(vis, pos, data, len); + view_cursors_scroll_to(c, pos + len); + } +} + +void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) { + size_t chars = 0; + for (size_t i = 0; i < len; i++) { + if (ISUTF8(data[i])) + chars++; + } + + Text *txt = vis->win->file->text; + Iterator it = text_iterator_get(txt, pos); + for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n'; ) + text_iterator_char_next(&it, NULL); + + text_delete(txt, pos, it.pos - pos); + vis_insert(vis, pos, data, len); +} + +void vis_replace_key(Vis *vis, const char *data, size_t len) { + for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) { + size_t pos = view_cursors_pos(c); + vis_replace(vis, pos, data, len); + view_cursors_scroll_to(c, pos + len); + } +} + +void vis_delete(Vis *vis, size_t pos, size_t len) { + text_delete(vis->win->file->text, pos, len); + windows_invalidate(vis, pos, pos + len); +} + +void vis_prompt_show(Vis *vis, const char *title, const char *text) { + if (vis->prompt_window) + return; + vis->prompt_window = vis->win; + vis->win = vis->prompt; + vis->prompt_type = title[0]; + vis->ui->prompt(vis->ui, title, text); +} + +void vis_prompt_hide(Vis *vis) { + if (!vis->prompt_window) + return; + vis->ui->prompt_hide(vis->ui); + vis->win = vis->prompt_window; + vis->prompt_window = NULL; +} + +char *vis_prompt_get(Vis *vis) { + return vis->ui->prompt_input(vis->ui); +} + +void vis_info_show(Vis *vis, const char *msg, ...) { + va_list ap; + va_start(ap, msg); + vis->ui->info(vis->ui, msg, ap); + va_end(ap); +} + +void vis_info_hide(Vis *vis) { + vis->ui->info_hide(vis->ui); +} + /** operators */ static size_t op_change(Vis*, Text*, OperatorContext *c); static size_t op_yank(Vis*, Text*, OperatorContext *c); @@ -338,7 +810,7 @@ static bool cmd_new(Vis*, Filerange*, enum CmdOpt, const char *argv[]); static bool cmd_vnew(Vis*, Filerange*, enum CmdOpt, const char *argv[]); /* save the file displayed in the current window and close it */ static bool cmd_wq(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* save the file displayed in the current window if it was changed, then close the window */ +/* save the file displayed in the current window if it was changvis, then close the window */ static bool cmd_xit(Vis*, Filerange*, enum CmdOpt, const char *argv[]); /* save the file displayed in the current window to the name given. * do not change internal filname association. further :w commands @@ -357,11 +829,50 @@ static bool cmd_help(Vis*, Filerange*, enum CmdOpt, const char *argv[]); static void action_reset(Vis*, Action *a); static void vis_mode_set(Vis*, Mode *new_mode); -static bool vis_window_new(Vis*, const char *file); -static bool vis_window_split(Win *win); #include "config.h" +static Mode *mode_get(Vis *vis, enum VisMode mode) { + if (mode < LENGTH(vis_modes)) + return &vis_modes[mode]; + return NULL; +} + +static bool mode_map(Mode *mode, const char *name, KeyBinding *binding) { + return map_put(mode->bindings, name, binding); +} + +bool vis_mode_map(Vis *vis, enum VisMode modeid, const char *name, KeyBinding *binding) { + Mode *mode = mode_get(vis, modeid); + return mode && map_put(mode->bindings, name, binding); +} + +static bool mode_bindings(Mode *mode, KeyBinding **bindings) { + if (!mode->bindings) + mode->bindings = map_new(); + if (!mode->bindings) + return false; + bool success = true; + for (KeyBinding *kb = *bindings; kb->key; kb++) { + if (!mode_map(mode, kb->key, kb)) + success = false; + } + return success; +} + +bool vis_mode_unmap(Vis *vis, enum VisMode modeid, const char *name) { + Mode *mode = mode_get(vis, modeid); + return mode && map_delete(mode->bindings, name); +} + +bool vis_action_register(Vis *vis, KeyAction *action) { + if (!vis->actions) + vis->actions = map_new(); + if (!vis->actions) + return false; + return map_put(vis->actions, action->name, action); +} + static const char *getkey(Vis*); static void action_do(Vis*, Action *a); static bool exec_command(Vis *vis, char type, const char *cmdline); @@ -431,7 +942,7 @@ static size_t op_put(Vis *vis, Text *txt, OperatorContext *c) { static const char *expand_tab(Vis *vis) { static char spaces[9]; - int tabwidth = editor_tabwidth_get(vis); + int tabwidth = tabwidth_get(vis); tabwidth = MIN(tabwidth, LENGTH(spaces) - 1); for (int i = 0; i < tabwidth; i++) spaces[i] = ' '; @@ -459,7 +970,7 @@ static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) { static size_t op_shift_left(Vis *vis, Text *txt, OperatorContext *c) { size_t pos = text_line_begin(txt, c->range.end), prev_pos; - size_t tabwidth = editor_tabwidth_get(vis), tablen; + size_t tabwidth = tabwidth_get(vis), tablen; /* if range ends at the begin of a line, skip line break */ if (pos == c->range.end) @@ -563,7 +1074,7 @@ static size_t op_repeat_insert(Vis *vis, Text *txt, OperatorContext *c) { static size_t op_repeat_replace(Vis *vis, Text *txt, OperatorContext *c) { const char *data = vis->buffer_repeat.data; size_t len = vis->buffer_repeat.len; - editor_replace(vis, c->pos, data, len); + vis_replace(vis, c->pos, data, len); return c->pos + len; } @@ -748,7 +1259,7 @@ static const char *macro_record(Vis *vis, const char *keys, const Arg *arg) { enum VisMacro macro; keys = key2macro(vis, keys, ¯o); vis_macro_record(vis, macro); - editor_draw(vis); + vis_draw(vis); return keys; } @@ -760,7 +1271,7 @@ static const char *macro_replay(Vis *vis, const char *keys, const Arg *arg) { } static const char *suspend(Vis *vis, const char *keys, const Arg *arg) { - editor_suspend(vis); + vis_suspend(vis); return keys; } @@ -881,7 +1392,7 @@ static const char *replace(Vis *vis, const char *keys, const Arg *arg) { action_reset(vis, &vis->action_prev); vis->action_prev.op = &ops[OP_REPEAT_REPLACE]; buffer_put(&vis->buffer_repeat, keys, len); - editor_replace_key(vis, keys, len); + vis_replace_key(vis, keys, len); text_snapshot(vis->win->file->text); return next; } @@ -974,7 +1485,7 @@ static const char *reg(Vis *vis, const char *keys, const Arg *arg) { } static const char *key2mark(Vis *vis, const char *keys, int *mark) { - *mark = MARK_INVALID; + *mark = VIS_MARK_INVALID; if (!keys[0]) return NULL; if (keys[0] >= 'a' && keys[0] <= 'z') @@ -1007,7 +1518,7 @@ static const char *undo(Vis *vis, const char *keys, const Arg *arg) { if (view_cursors_count(view) == 1) view_cursor_to(view, pos); /* redraw all windows in case some display the same file */ - editor_draw(vis); + vis_draw(vis); } return keys; } @@ -1019,7 +1530,7 @@ static const char *redo(Vis *vis, const char *keys, const Arg *arg) { if (view_cursors_count(view) == 1) view_cursor_to(view, pos); /* redraw all windows in case some display the same file */ - editor_draw(vis); + vis_draw(vis); } return keys; } @@ -1029,7 +1540,7 @@ static const char *earlier(Vis *vis, const char *keys, const Arg *arg) { if (pos != EPOS) { view_cursor_to(vis->win->view, pos); /* redraw all windows in case some display the same file */ - editor_draw(vis); + vis_draw(vis); } return keys; } @@ -1039,7 +1550,7 @@ static const char *later(Vis *vis, const char *keys, const Arg *arg) { if (pos != EPOS) { view_cursor_to(vis->win->view, pos); /* redraw all windows in case some display the same file */ - editor_draw(vis); + vis_draw(vis); } return keys; } @@ -1056,26 +1567,26 @@ static const char *insert_register(Vis *vis, const char *keys, const Arg *arg) { Register *reg = vis_register_get(vis, regid); if (reg) { int pos = view_cursor_get(vis->win->view); - editor_insert(vis, pos, reg->data, reg->len); + vis_insert(vis, pos, reg->data, reg->len); view_cursor_to(vis->win->view, pos + reg->len); } return keys; } static const char *prompt_search(Vis *vis, const char *keys, const Arg *arg) { - editor_prompt_show(vis, arg->s, ""); + vis_prompt_show(vis, arg->s, ""); vis_mode_switch(vis, VIS_MODE_PROMPT); return keys; } static const char *prompt_cmd(Vis *vis, const char *keys, const Arg *arg) { - editor_prompt_show(vis, ":", arg->s); + vis_prompt_show(vis, ":", arg->s); vis_mode_switch(vis, VIS_MODE_PROMPT); return keys; } static const char *prompt_enter(Vis *vis, const char *keys, const Arg *arg) { - char *s = editor_prompt_get(vis); + char *s = vis_prompt_get(vis); /* it is important to switch back to the previous mode, which hides * the prompt and more importantly resets vis->win to the currently * focused editor window *before* anything is executed which depends @@ -1085,12 +1596,12 @@ static const char *prompt_enter(Vis *vis, const char *keys, const Arg *arg) { if (s && *s && exec_command(vis, vis->prompt_type, s) && vis->running) vis_mode_switch(vis, VIS_MODE_NORMAL); free(s); - editor_draw(vis); + vis_draw(vis); return keys; } static const char *prompt_backspace(Vis *vis, const char *keys, const Arg *arg) { - char *cmd = editor_prompt_get(vis); + char *cmd = vis_prompt_get(vis); if (!cmd || !*cmd) prompt_enter(vis, keys, NULL); else @@ -1161,7 +1672,7 @@ static const char *insert_verbatim(Vis *vis, const char *keys, const Arg *arg) { if (len > 0) { size_t pos = view_cursor_get(vis->win->view); - editor_insert(vis, pos, buf, len); + vis_insert(vis, pos, buf, len); view_cursor_to(vis->win->view, pos + len); } return keys; @@ -1219,7 +1730,7 @@ static const char *window(Vis *vis, const char *keys, const Arg *arg) { } static const char *insert(Vis *vis, const char *keys, const Arg *arg) { - editor_insert_key(vis, arg->s, arg->s ? strlen(arg->s) : 0); + vis_insert_key(vis, arg->s, arg->s ? strlen(arg->s) : 0); return keys; } @@ -1242,7 +1753,7 @@ static void copy_indent_from_previous_line(Win *win) { if (!buf) return; len = text_bytes_get(text, begin, len, buf); - editor_insert_key(win->editor, buf, len); + vis_insert_key(win->editor, buf, len); free(buf); } @@ -1431,7 +1942,7 @@ static void action_do(Vis *vis, Action *a) { else if (vis->mode->visual) vis_mode_switch(vis, VIS_MODE_NORMAL); text_snapshot(txt); - editor_draw(vis); + vis_draw(vis); } if (a != &vis->action_prev) { @@ -1530,7 +2041,7 @@ static bool cmd_set(Vis *vis, Filerange *range, enum CmdOpt cmdopt, const char * } if (!argv[1]) { - editor_info_show(vis, "Expecting: set option [value]"); + vis_info_show(vis, "Expecting: set option [value]"); return false; } @@ -1549,14 +2060,14 @@ static bool cmd_set(Vis *vis, Filerange *range, enum CmdOpt cmdopt, const char * if (!opt) opt = map_closest(vis->options, argv[1]); if (!opt) { - editor_info_show(vis, "Unknown option: `%s'", argv[1]); + vis_info_show(vis, "Unknown option: `%s'", argv[1]); return false; } switch (opt->type) { case OPTION_TYPE_STRING: if (!opt->optional && !argv[2]) { - editor_info_show(vis, "Expecting string option value"); + vis_info_show(vis, "Expecting string option value"); return false; } break; @@ -1564,7 +2075,7 @@ static bool cmd_set(Vis *vis, Filerange *range, enum CmdOpt cmdopt, const char * if (!argv[2]) { arg.b = true; } else if (!parse_bool(argv[2], &arg.b)) { - editor_info_show(vis, "Expecting boolean option value not: `%s'", argv[2]); + vis_info_show(vis, "Expecting boolean option value not: `%s'", argv[2]); return false; } if (invert) @@ -1572,7 +2083,7 @@ static bool cmd_set(Vis *vis, Filerange *range, enum CmdOpt cmdopt, const char * break; case OPTION_TYPE_NUMBER: if (!argv[2]) { - editor_info_show(vis, "Expecting number"); + vis_info_show(vis, "Expecting number"); return false; } /* TODO: error checking? long type */ @@ -1588,15 +2099,15 @@ static bool cmd_set(Vis *vis, Filerange *range, enum CmdOpt cmdopt, const char * vis->autoindent = arg.b; break; case OPTION_TABWIDTH: - editor_tabwidth_set(vis, arg.i); + tabwidth_set(vis, arg.i); break; case OPTION_SYNTAX: if (!argv[2]) { Syntax *syntax = view_syntax_get(vis->win->view); if (syntax) - editor_info_show(vis, "Syntax definition in use: `%s'", syntax->name); + vis_info_show(vis, "Syntax definition in use: `%s'", syntax->name); else - editor_info_show(vis, "No syntax definition in use"); + vis_info_show(vis, "No syntax definition in use"); return true; } @@ -1610,11 +2121,11 @@ static bool cmd_set(Vis *vis, Filerange *range, enum CmdOpt cmdopt, const char * if (parse_bool(argv[2], &arg.b) && !arg.b) view_syntax_set(vis->win->view, NULL); else - editor_info_show(vis, "Unknown syntax definition: `%s'", argv[2]); + vis_info_show(vis, "Unknown syntax definition: `%s'", argv[2]); break; case OPTION_SHOW: if (!argv[2]) { - editor_info_show(vis, "Expecting: spaces, tabs, newlines"); + vis_info_show(vis, "Expecting: spaces, tabs, newlines"); return false; } char *keys[] = { "spaces", "tabs", "newlines" }; @@ -1720,7 +2231,7 @@ static bool openfiles(Vis *vis, const char **files) { return false; errno = 0; if (!vis_window_new(vis, file)) { - editor_info_show(vis, "Could not open `%s' %s", file, + vis_info_show(vis, "Could not open `%s' %s", file, errno ? strerror(errno) : ""); return false; } @@ -1741,7 +2252,7 @@ static bool is_view_closeable(Win *win) { } static void info_unsaved_changes(Vis *vis) { - editor_info_show(vis, "No write since last change (add ! to override)"); + vis_info_show(vis, "No write since last change (add ! to override)"); } static bool cmd_edit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { @@ -1751,11 +2262,11 @@ static bool cmd_edit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *ar return false; } if (!argv[1]) - return editor_window_reload(oldwin); + return vis_window_reload(oldwin); if (!openfiles(vis, &argv[1])) return false; if (vis->win != oldwin) - editor_window_close(oldwin); + vis_window_close(oldwin); return vis->win != oldwin; } @@ -1764,7 +2275,7 @@ static bool cmd_quit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *ar info_unsaved_changes(vis); return false; } - editor_window_close(vis->win); + vis_window_close(vis->win); if (!vis->windows) quit(vis, NULL, NULL); return true; @@ -1787,7 +2298,7 @@ static bool cmd_bdelete(Vis *vis, Filerange *range, enum CmdOpt opt, const char for (Win *next, *win = vis->windows; win; win = next) { next = win->next; if (win->file->text == txt) - editor_window_close(win); + vis_window_close(win); } if (!vis->windows) quit(vis, NULL, NULL); @@ -1798,7 +2309,7 @@ static bool cmd_qall(Vis *vis, Filerange *range, enum CmdOpt opt, const char *ar for (Win *next, *win = vis->windows; win; win = next) { next = win->next; if (!text_modified(vis->win->file->text) || (opt & CMD_OPT_FORCE)) - editor_window_close(win); + vis_window_close(win); } if (!vis->windows) quit(vis, NULL, NULL); @@ -1811,7 +2322,7 @@ static bool cmd_read(Vis *vis, Filerange *range, enum CmdOpt opt, const char *ar char cmd[255]; if (!argv[1]) { - editor_info_show(vis, "Filename or command expected"); + vis_info_show(vis, "Filename or command expected"); return false; } @@ -1841,7 +2352,7 @@ static bool cmd_substitute(Vis *vis, Filerange *range, enum CmdOpt opt, const ch static bool cmd_split(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { enum UiOption options = view_options_get(vis->win->view); - editor_windows_arrange(vis, UI_LAYOUT_HORIZONTAL); + windows_arrange(vis, UI_LAYOUT_HORIZONTAL); if (!argv[1]) return vis_window_split(vis->win); bool ret = openfiles(vis, &argv[1]); @@ -1851,7 +2362,7 @@ static bool cmd_split(Vis *vis, Filerange *range, enum CmdOpt opt, const char *a static bool cmd_vsplit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { enum UiOption options = view_options_get(vis->win->view); - editor_windows_arrange(vis, UI_LAYOUT_VERTICAL); + windows_arrange(vis, UI_LAYOUT_VERTICAL); if (!argv[1]) return vis_window_split(vis->win); bool ret = openfiles(vis, &argv[1]); @@ -1860,12 +2371,12 @@ static bool cmd_vsplit(Vis *vis, Filerange *range, enum CmdOpt opt, const char * } static bool cmd_new(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - editor_windows_arrange(vis, UI_LAYOUT_HORIZONTAL); + windows_arrange(vis, UI_LAYOUT_HORIZONTAL); return vis_window_new(vis, NULL); } static bool cmd_vnew(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - editor_windows_arrange(vis, UI_LAYOUT_VERTICAL); + windows_arrange(vis, UI_LAYOUT_VERTICAL); return vis_window_new(vis, NULL); } @@ -1887,32 +2398,32 @@ static bool cmd_write(Vis *vis, Filerange *range, enum CmdOpt opt, const char *a if (strchr(argv[0], 'q')) { ssize_t written = text_write_range(text, range, STDOUT_FILENO); if (written == -1 || (size_t)written != text_range_size(range)) { - editor_info_show(vis, "Can not write to stdout"); + vis_info_show(vis, "Can not write to stdout"); return false; } /* make sure the file is marked as saved i.e. not modified */ text_save_range(text, range, NULL); return true; } - editor_info_show(vis, "No filename given, use 'wq' to write to stdout"); + vis_info_show(vis, "No filename given, use 'wq' to write to stdout"); return false; } - editor_info_show(vis, "Filename expected"); + vis_info_show(vis, "Filename expected"); return false; } for (const char **name = &argv[1]; *name; name++) { struct stat meta; if (!(opt & CMD_OPT_FORCE) && file->stat.st_mtime && stat(*name, &meta) == 0 && file->stat.st_mtime < meta.st_mtime) { - editor_info_show(vis, "WARNING: file has been changed since reading it"); + vis_info_show(vis, "WARNING: file has been changed since reading it"); return false; } if (!text_save_range(text, range, *name)) { - editor_info_show(vis, "Can't write `%s'", *name); + vis_info_show(vis, "Can't write `%s'", *name); return false; } if (!file->name) { - editor_window_name(vis->win, *name); + window_name(vis->win, *name); file->name = vis->win->file->name; } if (strcmp(file->name, *name) == 0) @@ -1923,7 +2434,7 @@ static bool cmd_write(Vis *vis, Filerange *range, enum CmdOpt opt, const char *a static bool cmd_saveas(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { if (cmd_write(vis, range, opt, argv)) { - editor_window_name(vis->win, argv[1]); + window_name(vis->win, argv[1]); vis->win->file->stat = text_stat(vis->win->file->text); return true; } @@ -1965,7 +2476,7 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * close(pout[1]); close(perr[0]); close(perr[1]); - editor_info_show(vis, "fork failure: %s", strerror(errno)); + vis_info_show(vis, "fork failure: %s", strerror(errno)); return false; } else if (pid == 0) { /* child i.e filter */ if (!interactive) @@ -1983,7 +2494,7 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * execl("/bin/sh", "sh", "-c", argv[1], NULL); else execvp(argv[1], (char**)argv+1); - editor_info_show(vis, "exec failure: %s", strerror(errno)); + vis_info_show(vis, "exec failure: %s", strerror(errno)); exit(EXIT_FAILURE); } @@ -2024,7 +2535,7 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * do { if (vis->cancel_filter) { kill(-pid, SIGTERM); - editor_info_show(vis, "Command cancelled"); + vis_info_show(vis, "Command cancelled"); break; } @@ -2040,7 +2551,7 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) { if (errno == EINTR) continue; - editor_info_show(vis, "Select failure"); + vis_info_show(vis, "Select failure"); break; } @@ -2059,7 +2570,7 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * close(pin[1]); pin[1] = -1; if (len == -1) - editor_info_show(vis, "Error writing to external command"); + vis_info_show(vis, "Error writing to external command"); } } @@ -2073,7 +2584,7 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * close(pout[0]); pout[0] = -1; } else if (errno != EINTR && errno != EWOULDBLOCK) { - editor_info_show(vis, "Error reading from filter stdout"); + vis_info_show(vis, "Error reading from filter stdout"); close(pout[0]); pout[0] = -1; } @@ -2088,7 +2599,7 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * close(perr[0]); perr[0] = -1; } else if (errno != EINTR && errno != EWOULDBLOCK) { - editor_info_show(vis, "Error reading from filter stderr"); + vis_info_show(vis, "Error reading from filter stderr"); close(pout[0]); pout[0] = -1; } @@ -2116,11 +2627,11 @@ static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char * if (!vis->cancel_filter) { if (status == 0) - editor_info_show(vis, "Command succeded"); + vis_info_show(vis, "Command succeded"); else if (errmsg.len > 0) - editor_info_show(vis, "Command failed: %s", errmsg.data); + vis_info_show(vis, "Command failed: %s", errmsg.data); else - editor_info_show(vis, "Command failed"); + vis_info_show(vis, "Command failed"); } vis->ui->terminal_restore(vis->ui); @@ -2136,7 +2647,7 @@ static bool cmd_earlier_later(Vis *vis, Filerange *range, enum CmdOpt opt, const errno = 0; count = strtol(argv[1], &unit, 10); if (errno || unit == argv[1] || count < 0) { - editor_info_show(vis, "Invalid number"); + vis_info_show(vis, "Invalid number"); return false; } @@ -2149,7 +2660,7 @@ static bool cmd_earlier_later(Vis *vis, Filerange *range, enum CmdOpt opt, const case 'm': count *= 60; /* fall through */ case 's': break; default: - editor_info_show(vis, "Unknown time specifier (use: s,m,h or d)"); + vis_info_show(vis, "Unknown time specifier (use: s,m,h or d)"); return false; } @@ -2170,7 +2681,7 @@ static bool cmd_earlier_later(Vis *vis, Filerange *range, enum CmdOpt opt, const time_t state = text_state(txt); char buf[32]; strftime(buf, sizeof buf, "State from %H:%M", localtime(&state)); - editor_info_show(vis, "%s", buf); + vis_info_show(vis, "%s", buf); return pos != EPOS; } @@ -2348,7 +2859,7 @@ bool vis_cmd(Vis *vis, const char *cmdline) { } if (name != line) { - editor_info_show(vis, "Invalid range\n"); + vis_info_show(vis, "Invalid range\n"); free(line); return false; } @@ -2374,7 +2885,7 @@ bool vis_cmd(Vis *vis, const char *cmdline) { Command *cmd = lookup_cmd(vis, name); if (!cmd) { - editor_info_show(vis, "Not an editor command"); + vis_info_show(vis, "Not an editor command"); free(line); return false; } @@ -2429,29 +2940,6 @@ static bool exec_command(Vis *vis, char type, const char *cmd) { return false; } -static void settings_apply(Vis *vis, const char **settings) { - for (const char **opt = settings; opt && *opt; opt++) - vis_cmd(vis, *opt); -} - -static bool vis_window_new(Vis *vis, const char *file) { - if (!editor_window_new(vis, file)) - return false; - Syntax *s = view_syntax_get(vis->win->view); - if (s) - settings_apply(vis, s->settings); - return true; -} - -static bool vis_window_split(Win *win) { - if (!editor_window_split(win)) - return false; - Syntax *s = view_syntax_get(win->view); - if (s) - settings_apply(win->editor, s->settings); - return true; -} - void vis_die(Vis *vis, const char *msg, ...) { va_list ap; va_start(ap, msg); @@ -2566,7 +3054,7 @@ static const char *getkey(Vis *vis) { const char *key = vis->ui->getkey(vis->ui); if (!key) return NULL; - editor_info_hide(vis); + vis_info_hide(vis); if (vis->recording) macro_append(vis->recording, key); return key; @@ -2652,7 +3140,7 @@ void vis_run(Vis *vis, int argc, char *argv[]) { sigset_t emptyset; sigemptyset(&emptyset); - editor_draw(vis); + vis_draw(vis); vis->running = true; sigsetjmp(vis->sigbus_jmpbuf, 1); @@ -2669,18 +3157,18 @@ void vis_run(Vis *vis, int argc, char *argv[]) { if (win->file->truncated) { free(name); name = strdup(win->file->name); - editor_window_close(win); + vis_window_close(win); } } if (!vis->windows) vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-"); else - editor_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-"); + vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-"); vis->sigbus = false; free(name); } - editor_update(vis); + vis_update(vis); idle.tv_sec = vis->mode->idle_timeout; int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset); if (r == -1 && errno == EINTR) @@ -2711,24 +3199,24 @@ void vis_run(Vis *vis, int argc, char *argv[]) { } Vis *vis_new(Ui *ui) { - Vis *vis = editor_new(ui); + Vis *vis = vis_new0(ui); if (!vis) return NULL; for (int i = 0; i < LENGTH(vis_modes); i++) { Mode *mode = &vis_modes[i]; - if (!editor_mode_bindings(mode, &mode->default_bindings)) + if (!mode_bindings(mode, &mode->default_bindings)) vis_die(vis, "Could not load bindings for mode: %s\n", mode->name); } vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL]; - if (!editor_syntax_load(vis, syntaxes)) + if (!vis_syntax_load(vis, syntaxes)) vis_die(vis, "Could not load syntax highlighting definitions\n"); for (int i = 0; i < LENGTH(vis_action); i++) { KeyAction *action = &vis_action[i]; - if (!editor_action_register(vis, action)) + if (!vis_action_register(vis, action)) vis_die(vis, "Could not register action: %s\n", action->name); } @@ -2812,7 +3300,7 @@ void vis_motion(Vis *vis, enum VisMotion motion, ...) { case MOVE_MARK_LINE: { int mark = va_arg(ap, int); - if (MARK_a <= mark && mark < MARK_INVALID) + if (MARK_a <= mark && mark < VIS_MARK_INVALID) vis->action.mark = mark; else goto out; @@ -1,13 +1,101 @@ #ifndef VIS_H #define VIS_H +#include <signal.h> +#include <stddef.h> +#include <stdbool.h> +#include <setjmp.h> + +typedef struct Vis Vis; +typedef struct File File; +typedef struct Win Win; + #include "ui.h" -#include "editor.h" +#include "view.h" +#include "register.h" +#include "macro.h" +#include "syntax.h" +#include "ring-buffer.h" +#include "map.h" +#include "text-regex.h" + + +typedef union { + bool b; + int i; + const char *s; + void (*w)(View*); /* generic window commands */ + void (*f)(Vis*); /* generic editor commands */ +} Arg; + +typedef struct { + const char *name; + const char *help; + const char* (*func)(Vis*, const char *keys, const Arg*); + /* returns a pointer to the first not consumed character in keys + * or NULL if not enough input was available to complete the command */ + const Arg arg; + +} KeyAction; + +typedef struct { + const char *key; + KeyAction *action; + const char *alias; +} KeyBinding; + -typedef Editor Vis; Vis *vis_new(Ui*); -#define vis_free editor_free +void vis_free(Vis*); +void vis_resize(Vis*); +void vis_draw(Vis*); +void vis_update(Vis*); +void vis_suspend(Vis*); + +/* load a set of syntax highlighting definitions which will be associated + * to the underlying window based on the file type loaded. + * + * The parameter `syntaxes' has to point to a NULL terminated array. + */ +bool vis_syntax_load(Vis*, Syntax *syntaxes); +void vis_syntax_unload(Vis*); + +/* creates a new window, and loads the given file. if filename is NULL + * an unamed / empty buffer is created. If the given file is already opened + * in another window, share the underlying text that is changes will be + * visible in both windows */ +bool vis_window_new(Vis*, const char *filename); +/* reload the file currently displayed in the window from disk */ +bool vis_window_reload(Win*); +void vis_window_close(Win*); +/* split the given window. changes to the displayed text will be reflected + * in both windows */ +bool vis_window_split(Win*); +/* focus the next / previous window */ +void vis_window_next(Vis*); +void vis_window_prev(Vis*); +/* display a user prompt with a certain title and default text */ +void vis_prompt_show(Vis*, const char *title, const char *text); +/* hide the user prompt if it is currently shown */ +void vis_prompt_hide(Vis*); +/* return the content of the command prompt in a malloc(3)-ed string + * which the call site has to free. */ +char *vis_prompt_get(Vis*); +/* replace the current command line content with the one given */ +void vis_prompt_set(Vis*, const char *line); + +/* display a message to the user */ +void vis_info_show(Vis*, const char *msg, ...); +void vis_info_hide(Vis*); + +/* these function operate on the currently focused window but make sure + * that all windows which show the affected region are redrawn too. */ +void vis_insert_key(Vis*, const char *data, size_t len); +void vis_replace_key(Vis*, const char *data, size_t len); +void vis_insert(Vis*, size_t pos, const char *data, size_t len); +void vis_delete(Vis*, size_t pos, size_t len); +void vis_replace(Vis*, size_t pos, const char *data, size_t len); void vis_run(Vis*, int argc, char *argv[]); void vis_die(Vis*, const char *msg, ...); @@ -29,6 +117,10 @@ enum VisMode { }; void vis_mode_switch(Vis*, enum VisMode); +bool vis_mode_map(Vis*, enum VisMode, const char *name, KeyBinding*); +bool vis_mode_unmap(Vis*, enum VisMode, const char *name); + +bool vis_action_register(Vis*, KeyAction*); enum VisOperator { OP_DELETE, @@ -162,15 +254,45 @@ bool vis_macro_record_stop(Vis*); bool vis_macro_replay(Vis*, enum VisMacro); enum VisMark { - /* TODO: temporary */ + 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, + MARK_SELECTION_START, + MARK_SELECTION_END, VIS_MARK_INVALID, }; void vis_mark_set(Vis*, enum VisMark mark, size_t pos); enum VisRegister { - /* TODO: temporary */ - VIS_REGISTER_INVALID = REG_LAST, + 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, + VIS_REGISTER_INVALID, }; void vis_register_set(Vis*, enum VisRegister); @@ -186,4 +308,110 @@ const char *vis_keys(Vis*, const char *input); bool vis_signal_handler(Vis*, int signum, const siginfo_t *siginfo, const void *context); +/* TODO: temporary */ +typedef struct Operator Operator; +typedef struct Movement Movement; +typedef struct TextObject TextObject; + +typedef struct { /** collects all information until an operator is executed */ + int count; + enum VisMotionType type; + const Operator *op; + const Movement *movement; + const TextObject *textobj; + Register *reg; + enum VisMark mark; + Arg arg; +} Action; + +/* a mode contains a set of key bindings which are currently valid. + * + * each mode can specify one parent mode which is consultated if a given key + * is not found in the current mode. hence the modes form a tree which is + * searched from the current mode up towards the root mode until a valid binding + * is found. + * + * if no binding is found, mode->input(...) is called and the user entered + * keys are passed as argument. this is used to change the document content. + */ +typedef struct Mode Mode; +struct Mode { + Mode *parent; /* if no match is found in this mode, search will continue there */ + Map *bindings; + KeyBinding *default_bindings; + const char *name; /* descriptive, user facing name of the mode */ + const char *status; /* name displayed in the window status bar */ + const char *help; /* short description used by :help */ + bool isuser; /* whether this is a user or internal mode */ + void (*enter)(Vis*, Mode *old); /* called right before the mode becomes active */ + void (*leave)(Vis*, Mode *new); /* called right before the mode becomes inactive */ + void (*input)(Vis*, const char*, size_t); /* called whenever a key is not found in this mode and all its parent modes */ + void (*idle)(Vis*); /* called whenever a certain idle time i.e. without any user input elapsed */ + time_t idle_timeout; /* idle time in seconds after which the registered function will be called */ + bool visual; /* whether text selection is possible in this mode */ +}; + +struct File { + Text *text; + const char *name; + volatile sig_atomic_t truncated; + bool is_stdin; + struct stat stat; + int refcount; + Mark marks[VIS_MARK_INVALID]; + File *next, *prev; +}; + +typedef struct { + time_t state; /* state of the text, used to invalidate change list */ + size_t index; /* #number of changes */ + size_t pos; /* where the current change occured */ +} ChangeList; + +struct Win { + Vis *editor; /* editor instance to which this window belongs */ + UiWin *ui; + File *file; /* file being displayed in this window */ + View *view; /* currently displayed part of underlying text */ + ViewEvent events; + RingBuffer *jumplist; /* LRU jump management */ + ChangeList changelist; /* state for iterating through least recently changes */ + Win *prev, *next; /* neighbouring windows */ +}; + +struct Vis { + Ui *ui; + File *files; + Win *windows; /* list of windows */ + Win *win; /* currently active window */ + Syntax *syntaxes; /* NULL terminated array of syntax definitions */ + Register registers[VIS_REGISTER_INVALID]; /* register used for copy and paste */ + Macro macros[VIS_MACRO_INVALID]; /* recorded macros */ + Macro *recording, *last_recording;/* currently and least recently recorded macro */ + Win *prompt; /* 1-line height window to get user input */ + Win *prompt_window; /* window which was focused before prompt was shown */ + char prompt_type; /* command ':' or search '/','?' prompt */ + Regex *search_pattern; /* last used search pattern */ + char search_char[8]; /* last used character to search for via 'f', 'F', 't', 'T' */ + int last_totill; /* last to/till movement used for ';' and ',' */ + int tabwidth; /* how many spaces should be used to display a tab */ + bool expandtab; /* whether typed tabs should be converted to spaces */ + bool autoindent; /* whether indentation should be copied from previous line on newline */ + Map *cmds; /* ":"-commands, used for unique prefix queries */ + Map *options; /* ":set"-options */ + Buffer buffer_repeat; /* holds data to repeat last insertion/replacement */ + Buffer input_queue; /* holds pending input keys */ + + Action action; /* current action which is in progress */ + Action action_prev; /* last operator action used by the repeat '.' key */ + Mode *mode; /* currently active mode, used to search for keybindings */ + Mode *mode_prev; /* previsouly active user mode */ + Mode *mode_before_prompt; /* user mode which was active before entering prompt */ + volatile bool running; /* exit main loop once this becomes false */ + volatile sig_atomic_t cancel_filter; /* abort external command */ + volatile sig_atomic_t sigbus; + sigjmp_buf sigbus_jmpbuf; + Map *actions; /* built in special editor keys / commands */ +}; + #endif |
