build: include dvtm terminfo entries in standalone builds
[vis.git] / vis.c
blob48ec9ba30d79baebbb940ec3963f2c2ec173846c
1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <signal.h>
6 #include <stdarg.h>
7 #include <stdio.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <limits.h>
11 #include <ctype.h>
12 #include <time.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <sys/stat.h>
17 #include <sys/ioctl.h>
18 #include <sys/mman.h>
19 #include <pwd.h>
20 #include <libgen.h>
21 #include <termkey.h>
23 #include "vis.h"
24 #include "text-util.h"
25 #include "text-motions.h"
26 #include "text-objects.h"
27 #include "util.h"
28 #include "vis-core.h"
29 #include "sam.h"
31 const MarkDef vis_marks[] = {
32 [VIS_MARK_SELECTION_START] = { '<', VIS_HELP("Last selection start") },
33 [VIS_MARK_SELECTION_END] = { '>', VIS_HELP("Last selection end") },
36 const RegisterDef vis_registers[] = {
37 [VIS_REG_DEFAULT] = { '"', VIS_HELP("Unnamed register") },
38 [VIS_REG_ZERO] = { '0', VIS_HELP("Yank register") },
39 [VIS_REG_1] = { '1', VIS_HELP("1st sub-expression match") },
40 [VIS_REG_2] = { '2', VIS_HELP("2nd sub-expression match") },
41 [VIS_REG_3] = { '3', VIS_HELP("3rd sub-expression match") },
42 [VIS_REG_4] = { '4', VIS_HELP("4th sub-expression match") },
43 [VIS_REG_5] = { '5', VIS_HELP("5th sub-expression match") },
44 [VIS_REG_6] = { '6', VIS_HELP("6th sub-expression match") },
45 [VIS_REG_7] = { '7', VIS_HELP("7th sub-expression match") },
46 [VIS_REG_8] = { '8', VIS_HELP("8th sub-expression match") },
47 [VIS_REG_9] = { '9', VIS_HELP("9th sub-expression match") },
48 [VIS_REG_AMPERSAND] = { '&', VIS_HELP("Last regex match") },
49 [VIS_REG_BLACKHOLE] = { '_', VIS_HELP("/dev/null register") },
50 [VIS_REG_CLIPBOARD] = { '*', VIS_HELP("System clipboard register, see vis-clipboard(1)") },
51 [VIS_REG_DOT] = { '.', VIS_HELP("Last inserted text") },
52 [VIS_REG_SEARCH] = { '/', VIS_HELP("Last search pattern") },
53 [VIS_REG_COMMAND] = { ':', VIS_HELP("Last :-command") },
54 [VIS_REG_SHELL] = { '!', VIS_HELP("Last shell command given to either <, >, |, or !") },
57 static Macro *macro_get(Vis *vis, enum VisRegister);
58 static void macro_replay(Vis *vis, const Macro *macro);
59 static void macro_replay_internal(Vis *vis, const Macro *macro);
60 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record);
62 bool vis_event_emit(Vis *vis, enum VisEvents id, ...) {
63 if (!vis->event)
64 return true;
66 if (!vis->initialized) {
67 vis->initialized = true;
68 vis->ui->init(vis->ui, vis);
69 if (vis->event->init)
70 vis->event->init(vis);
73 va_list ap;
74 va_start(ap, id);
75 bool ret = true;
77 switch (id) {
78 case VIS_EVENT_INIT:
79 break;
80 case VIS_EVENT_START:
81 if (vis->event->start)
82 vis->event->start(vis);
83 break;
84 case VIS_EVENT_FILE_OPEN:
85 case VIS_EVENT_FILE_SAVE_PRE:
86 case VIS_EVENT_FILE_SAVE_POST:
87 case VIS_EVENT_FILE_CLOSE:
89 File *file = va_arg(ap, File*);
90 if (file->internal)
91 break;
92 if (id == VIS_EVENT_FILE_OPEN && vis->event->file_open) {
93 vis->event->file_open(vis, file);
94 } else if (id == VIS_EVENT_FILE_SAVE_PRE && vis->event->file_save_pre) {
95 const char *path = va_arg(ap, const char*);
96 ret = vis->event->file_save_pre(vis, file, path);
97 } else if (id == VIS_EVENT_FILE_SAVE_POST && vis->event->file_save_post) {
98 const char *path = va_arg(ap, const char*);
99 vis->event->file_save_post(vis, file, path);
100 } else if (id == VIS_EVENT_FILE_CLOSE && vis->event->file_close) {
101 vis->event->file_close(vis, file);
103 break;
105 case VIS_EVENT_WIN_OPEN:
106 case VIS_EVENT_WIN_CLOSE:
107 case VIS_EVENT_WIN_HIGHLIGHT:
108 case VIS_EVENT_WIN_SYNTAX:
109 case VIS_EVENT_WIN_STATUS:
111 Win *win = va_arg(ap, Win*);
112 if (win->file->internal)
113 break;
114 if (vis->event->win_open && id == VIS_EVENT_WIN_OPEN) {
115 vis->event->win_open(vis, win);
116 } else if (vis->event->win_close && id == VIS_EVENT_WIN_CLOSE) {
117 vis->event->win_close(vis, win);
118 } else if (vis->event->win_highlight && id == VIS_EVENT_WIN_HIGHLIGHT) {
119 vis->event->win_highlight(vis, win, win->horizon);
120 } else if (vis->event->win_syntax && id == VIS_EVENT_WIN_SYNTAX) {
121 const char *syntax = va_arg(ap, const char*);
122 ret = vis->event->win_syntax(vis, win, syntax);
123 } else if (vis->event->win_status && id == VIS_EVENT_WIN_STATUS) {
124 vis->event->win_status(vis, win);
126 break;
128 case VIS_EVENT_QUIT:
129 if (vis->event->quit)
130 vis->event->quit(vis);
131 break;
134 va_end(ap);
135 return ret;
138 /** window / file handling */
140 static void file_free(Vis *vis, File *file) {
141 if (!file)
142 return;
143 if (file->refcount > 1) {
144 --file->refcount;
145 return;
147 vis_event_emit(vis, VIS_EVENT_FILE_CLOSE, file);
148 text_free(file->text);
149 free((char*)file->name);
151 if (file->prev)
152 file->prev->next = file->next;
153 if (file->next)
154 file->next->prev = file->prev;
155 if (vis->files == file)
156 vis->files = file->next;
157 free(file);
160 static File *file_new_text(Vis *vis, Text *text) {
161 File *file = calloc(1, sizeof(*file));
162 if (!file)
163 return NULL;
164 file->fd = -1;
165 file->text = text;
166 file->stat = text_stat(text);
167 if (vis->files)
168 vis->files->prev = file;
169 file->next = vis->files;
170 vis->files = file;
171 return file;
174 static char *absolute_path(const char *name) {
175 if (!name)
176 return NULL;
177 char *copy1 = strdup(name);
178 char *copy2 = strdup(name);
179 char *path_absolute = NULL;
180 char path_normalized[PATH_MAX] = "";
182 if (!copy1 || !copy2)
183 goto err;
185 char *dir = dirname(copy1);
186 char *base = basename(copy2);
187 if (!(path_absolute = realpath(dir, NULL)))
188 goto err;
190 snprintf(path_normalized, sizeof(path_normalized)-1, "%s/%s",
191 path_absolute, base);
192 err:
193 free(copy1);
194 free(copy2);
195 free(path_absolute);
196 return path_normalized[0] ? strdup(path_normalized) : NULL;
199 static File *file_new(Vis *vis, const char *name) {
200 char *name_absolute = NULL;
201 if (name) {
202 if (!(name_absolute = absolute_path(name)))
203 return NULL;
204 File *existing = NULL;
205 /* try to detect whether the same file is already open in another window
206 * TODO: do this based on inodes */
207 for (File *file = vis->files; file; file = file->next) {
208 if (file->name && strcmp(file->name, name_absolute) == 0) {
209 existing = file;
210 break;
213 if (existing) {
214 free(name_absolute);
215 return existing;
219 File *file = NULL;
220 Text *text = text_load(name);
221 if (!text && name && errno == ENOENT)
222 text = text_load(NULL);
223 if (!text)
224 goto err;
225 if (!(file = file_new_text(vis, text)))
226 goto err;
227 file->name = name_absolute;
228 vis_event_emit(vis, VIS_EVENT_FILE_OPEN, file);
229 return file;
230 err:
231 free(name_absolute);
232 text_free(text);
233 file_free(vis, file);
234 return NULL;
237 static File *file_new_internal(Vis *vis, const char *filename) {
238 File *file = file_new(vis, filename);
239 if (file) {
240 file->refcount = 1;
241 file->internal = true;
243 return file;
246 void file_name_set(File *file, const char *name) {
247 if (name == file->name)
248 return;
249 free((char*)file->name);
250 file->name = absolute_path(name);
253 const char *file_name_get(File *file) {
254 /* TODO: calculate path relative to working directory, cache result */
255 if (!file->name)
256 return NULL;
257 char cwd[PATH_MAX];
258 if (!getcwd(cwd, sizeof cwd))
259 return file->name;
260 const char *path = strstr(file->name, cwd);
261 if (path != file->name)
262 return file->name;
263 size_t cwdlen = strlen(cwd);
264 return file->name[cwdlen] == '/' ? file->name+cwdlen+1 : file->name;
267 void vis_window_status(Win *win, const char *status) {
268 win->ui->status(win->ui, status);
271 static void windows_invalidate(Vis *vis, size_t start, size_t end) {
272 for (Win *win = vis->windows; win; win = win->next) {
273 if (vis->win->file == win->file) {
274 Filerange view = view_viewport_get(win->view);
275 if ((view.start <= start && start <= view.end) ||
276 (view.start <= end && end <= view.end))
277 view_draw(win->view);
282 void window_selection_save(Win *win) {
283 File *file = win->file;
284 Filerange sel = view_cursors_selection_get(view_cursors(win->view));
285 file->marks[VIS_MARK_SELECTION_START] = text_mark_set(file->text, sel.start);
286 file->marks[VIS_MARK_SELECTION_END] = text_mark_set(file->text, sel.end);
289 static void window_free(Win *win) {
290 if (!win)
291 return;
292 Vis *vis = win->vis;
293 for (Win *other = vis->windows; other; other = other->next) {
294 if (other->parent == win)
295 other->parent = NULL;
297 if (vis->ui)
298 vis->ui->window_free(win->ui);
299 view_free(win->view);
300 for (size_t i = 0; i < LENGTH(win->modes); i++)
301 map_free(win->modes[i].bindings);
302 ringbuf_free(win->jumplist);
303 free(win->lexer_name);
304 free(win);
307 static void window_draw_colorcolumn(Win *win) {
308 View *view = win->view;
309 int cc = view_colorcolumn_get(view);
310 if (cc <= 0)
311 return;
312 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_COLOR_COLUMN);
313 size_t lineno = 0;
314 int line_cols = 0; /* Track the number of columns we've passed on each line */
315 bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */
316 int width = view_width_get(view);
318 for (Line *l = view_lines_first(view); l; l = l->next) {
319 if (l->lineno != lineno) {
320 line_cols = 0;
321 line_cc_set = false;
322 lineno = l->lineno;
325 if (line_cc_set)
326 continue;
327 line_cols += width;
329 /* This screen line contains the cell we want to highlight */
330 if (line_cols >= cc) {
331 l->cells[(cc - 1) % width].style = style;
332 line_cc_set = true;
337 static void window_draw_cursorline(Win *win) {
338 Vis *vis = win->vis;
339 View *view = win->view;
340 enum UiOption options = view_options_get(view);
341 if (!(options & UI_OPTION_CURSOR_LINE))
342 return;
343 if (vis->mode->visual || vis->win != win)
344 return;
345 if (view_cursors_multiple(view))
346 return;
348 int width = view_width_get(view);
349 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_CURSOR_LINE);
350 Cursor *cursor = view_cursors_primary_get(view);
351 size_t lineno = view_cursors_line_get(cursor)->lineno;
352 for (Line *l = view_lines_first(view); l; l = l->next) {
353 if (l->lineno == lineno) {
354 for (int x = 0; x < width; x++) {
355 l->cells[x].style.attr |= style.attr;
356 l->cells[x].style.bg = style.bg;
358 } else if (l->lineno > lineno) {
359 break;
364 static void window_draw_selection(View *view, Cursor *cur, CellStyle *style) {
365 Filerange sel = view_cursors_selection_get(cur);
366 if (!text_range_valid(&sel))
367 return;
368 Line *start_line; int start_col;
369 Line *end_line; int end_col;
370 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
371 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
372 if (!start_line && !end_line)
373 return;
374 if (!start_line) {
375 start_line = view_lines_first(view);
376 start_col = 0;
378 if (!end_line) {
379 end_line = view_lines_last(view);
380 end_col = end_line->width;
382 for (Line *l = start_line; l != end_line->next; l = l->next) {
383 int col = (l == start_line) ? start_col : 0;
384 int end = (l == end_line) ? end_col : l->width;
385 while (col < end) {
386 if (cell_color_equal(l->cells[col].style.fg, style->fg)) {
387 CellStyle old = l->cells[col].style;
388 l->cells[col].style.fg = old.bg;
389 l->cells[col].style.bg = old.fg;
390 } else {
391 l->cells[col].style.bg = style->bg;
393 col++;
398 static void window_draw_cursor_matching(Win *win, Cursor *cur, CellStyle *style) {
399 if (win->vis->mode->visual)
400 return;
401 Line *line_match; int col_match;
402 size_t pos = view_cursors_pos(cur);
403 size_t pos_match = text_bracket_match_symbol(win->file->text, pos, "(){}[]\"'`");
404 if (pos == pos_match)
405 return;
406 if (!view_coord_get(win->view, pos_match, &line_match, NULL, &col_match))
407 return;
408 if (cell_color_equal(line_match->cells[col_match].style.fg, style->fg)) {
409 CellStyle old = line_match->cells[col_match].style;
410 line_match->cells[col_match].style.fg = old.bg;
411 line_match->cells[col_match].style.bg = old.fg;
412 } else {
413 line_match->cells[col_match].style.bg = style->bg;
417 static void window_draw_cursor(Win *win, Cursor *cur, CellStyle *style, CellStyle *sel_style) {
418 if (win->vis->win != win)
419 return;
420 Line *line = view_cursors_line_get(cur);
421 int col = view_cursors_cell_get(cur);
422 if (!line || col == -1)
423 return;
424 line->cells[col].style = *style;
425 window_draw_cursor_matching(win, cur, sel_style);
426 return;
429 static void window_draw_cursors(Win *win) {
430 View *view = win->view;
431 Filerange viewport = view_viewport_get(view);
432 bool multiple_cursors = view_cursors_multiple(view);
433 Cursor *cursor = view_cursors_primary_get(view);
434 CellStyle style_cursor = win->ui->style_get(win->ui, UI_STYLE_CURSOR);
435 CellStyle style_cursor_primary = win->ui->style_get(win->ui, UI_STYLE_CURSOR_PRIMARY);
436 CellStyle style_selection = win->ui->style_get(win->ui, UI_STYLE_SELECTION);
437 for (Cursor *c = view_cursors_prev(cursor); c; c = view_cursors_prev(c)) {
438 window_draw_selection(win->view, c, &style_selection);
439 size_t pos = view_cursors_pos(c);
440 if (pos < viewport.start)
441 break;
442 window_draw_cursor(win, c, &style_cursor, &style_selection);
444 window_draw_selection(win->view, cursor, &style_selection);
445 window_draw_cursor(win, cursor, multiple_cursors ? &style_cursor_primary : &style_cursor, &style_selection);
446 for (Cursor *c = view_cursors_next(cursor); c; c = view_cursors_next(c)) {
447 window_draw_selection(win->view, c, &style_selection);
448 size_t pos = view_cursors_pos(c);
449 if (pos > viewport.end)
450 break;
451 window_draw_cursor(win, c, &style_cursor, &style_selection);
455 static void window_draw_eof(Win *win) {
456 View *view = win->view;
457 if (view_width_get(view) == 0)
458 return;
459 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_EOF);
460 for (Line *l = view_lines_last(view)->next; l; l = l->next) {
461 strcpy(l->cells[0].data, "~");
462 l->cells[0].style = style;
466 void vis_window_draw(Win *win) {
467 if (!win->ui || !view_update(win->view))
468 return;
469 Vis *vis = win->vis;
470 vis_event_emit(vis, VIS_EVENT_WIN_HIGHLIGHT, win);
472 window_draw_colorcolumn(win);
473 window_draw_cursorline(win);
474 window_draw_cursors(win);
475 window_draw_eof(win);
477 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, win);
480 Win *window_new_file(Vis *vis, File *file, enum UiOption options) {
481 Win *win = calloc(1, sizeof(Win));
482 if (!win)
483 return NULL;
484 win->vis = vis;
485 win->file = file;
486 win->jumplist = ringbuf_alloc(31);
487 win->horizon = 1 << 15;
488 win->view = view_new(file->text);
489 win->ui = vis->ui->window_new(vis->ui, win, options);
490 if (!win->jumplist || !win->view || !win->ui) {
491 window_free(win);
492 return NULL;
494 file->refcount++;
495 view_tabwidth_set(win->view, vis->tabwidth);
497 if (vis->windows)
498 vis->windows->prev = win;
499 win->next = vis->windows;
500 vis->windows = win;
501 vis->win = win;
502 vis->ui->window_focus(win->ui);
503 for (size_t i = 0; i < LENGTH(win->modes); i++)
504 win->modes[i].parent = &vis_modes[i];
505 vis_event_emit(vis, VIS_EVENT_WIN_OPEN, win);
506 return win;
509 bool vis_window_reload(Win *win) {
510 const char *name = win->file->name;
511 if (!name)
512 return false; /* can't reload unsaved file */
513 /* temporarily unset file name, otherwise file_new returns the same File */
514 win->file->name = NULL;
515 File *file = file_new(win->vis, name);
516 win->file->name = name;
517 if (!file)
518 return false;
519 file_free(win->vis, win->file);
520 file->refcount = 1;
521 win->file = file;
522 view_reload(win->view, file->text);
523 return true;
526 bool vis_window_split(Win *original) {
527 Win *win = window_new_file(original->vis, original->file, UI_OPTION_STATUSBAR);
528 if (!win)
529 return false;
530 for (size_t i = 0; i < LENGTH(win->modes); i++) {
531 if (original->modes[i].bindings)
532 win->modes[i].bindings = map_new();
533 if (win->modes[i].bindings)
534 map_copy(win->modes[i].bindings, original->modes[i].bindings);
536 win->file = original->file;
537 vis_window_syntax_set(win, vis_window_syntax_get(original));
538 view_options_set(win->view, view_options_get(original->view));
539 view_cursor_to(win->view, view_cursor_get(original->view));
540 return true;
543 void vis_window_focus(Win *win) {
544 if (!win)
545 return;
546 Vis *vis = win->vis;
547 vis->win = win;
548 vis->ui->window_focus(win->ui);
551 void vis_window_next(Vis *vis) {
552 Win *sel = vis->win;
553 if (!sel)
554 return;
555 vis_window_focus(sel->next ? sel->next : vis->windows);
558 void vis_window_prev(Vis *vis) {
559 Win *sel = vis->win;
560 if (!sel)
561 return;
562 sel = sel->prev;
563 if (!sel)
564 for (sel = vis->windows; sel->next; sel = sel->next);
565 vis_window_focus(sel);
568 const char *vis_window_syntax_get(Win *win) {
569 return win->lexer_name;
572 bool vis_window_syntax_set(Win *win, const char *syntax) {
573 if (!vis_event_emit(win->vis, VIS_EVENT_WIN_SYNTAX, win, syntax))
574 return false;
575 view_options_set(win->view, view_options_get(win->view));
576 free(win->lexer_name);
577 win->lexer_name = syntax ? strdup(syntax) : NULL;
578 return !syntax || win->lexer_name;
581 int vis_window_width_get(const Win *win) {
582 return win->ui->window_width(win->ui);
585 int vis_window_height_get(const Win *win) {
586 return win->ui->window_height(win->ui);
589 void vis_draw(Vis *vis) {
590 for (Win *win = vis->windows; win; win = win->next)
591 view_draw(win->view);
594 void vis_redraw(Vis *vis) {
595 vis->ui->redraw(vis->ui);
598 void vis_update(Vis *vis) {
599 vis->ui->draw(vis->ui);
602 void vis_suspend(Vis *vis) {
603 vis->ui->suspend(vis->ui);
606 bool vis_window_new(Vis *vis, const char *filename) {
607 File *file = file_new(vis, filename);
608 if (!file)
609 return false;
610 Win *win = window_new_file(vis, file, UI_OPTION_STATUSBAR);
611 if (!win) {
612 file_free(vis, file);
613 return false;
616 return true;
619 bool vis_window_new_fd(Vis *vis, int fd) {
620 if (fd == -1)
621 return false;
622 if (!vis_window_new(vis, NULL))
623 return false;
624 vis->win->file->fd = fd;
625 return true;
628 bool vis_window_closable(Win *win) {
629 if (!win || !text_modified(win->file->text))
630 return true;
631 return win->file->refcount > 1;
634 void vis_window_swap(Win *a, Win *b) {
635 if (a == b || !a || !b)
636 return;
637 Vis *vis = a->vis;
638 Win *tmp = a->next;
639 a->next = b->next;
640 b->next = tmp;
641 if (a->next)
642 a->next->prev = a;
643 if (b->next)
644 b->next->prev = b;
645 tmp = a->prev;
646 a->prev = b->prev;
647 b->prev = tmp;
648 if (a->prev)
649 a->prev->next = a;
650 if (b->prev)
651 b->prev->next = b;
652 if (vis->windows == a)
653 vis->windows = b;
654 else if (vis->windows == b)
655 vis->windows = a;
656 vis->ui->window_swap(a->ui, b->ui);
657 if (vis->win == a)
658 vis_window_focus(b);
659 else if (vis->win == b)
660 vis_window_focus(a);
663 void vis_window_close(Win *win) {
664 if (!win)
665 return;
666 Vis *vis = win->vis;
667 vis_event_emit(vis, VIS_EVENT_WIN_CLOSE, win);
668 file_free(vis, win->file);
669 if (win->prev)
670 win->prev->next = win->next;
671 if (win->next)
672 win->next->prev = win->prev;
673 if (vis->windows == win)
674 vis->windows = win->next;
675 if (vis->win == win)
676 vis->win = win->next ? win->next : win->prev;
677 if (win == vis->message_window)
678 vis->message_window = NULL;
679 window_free(win);
680 if (vis->win)
681 vis->ui->window_focus(vis->win->ui);
682 vis_draw(vis);
685 Vis *vis_new(Ui *ui, VisEvent *event) {
686 if (!ui)
687 return NULL;
688 Vis *vis = calloc(1, sizeof(Vis));
689 if (!vis)
690 return NULL;
691 vis->exit_status = -1;
692 vis->ui = ui;
693 vis->tabwidth = 8;
694 vis->expandtab = false;
695 vis->change_colors = true;
696 vis->registers[VIS_REG_BLACKHOLE].type = REGISTER_BLACKHOLE;
697 vis->registers[VIS_REG_CLIPBOARD].type = REGISTER_CLIPBOARD;
698 array_init(&vis->motions);
699 array_init(&vis->textobjects);
700 array_init(&vis->bindings);
701 array_init(&vis->actions_user);
702 action_reset(&vis->action);
703 buffer_init(&vis->input_queue);
704 if (!(vis->command_file = file_new_internal(vis, NULL)))
705 goto err;
706 if (!(vis->search_file = file_new_internal(vis, NULL)))
707 goto err;
708 if (!(vis->error_file = file_new_internal(vis, NULL)))
709 goto err;
710 if (!(vis->actions = map_new()))
711 goto err;
712 if (!(vis->keymap = map_new()))
713 goto err;
714 if (!sam_init(vis))
715 goto err;
716 struct passwd *pw;
717 char *shell = getenv("SHELL");
718 if ((!shell || !*shell) && (pw = getpwuid(getuid())))
719 shell = pw->pw_shell;
720 if (!shell || !*shell)
721 shell = "/bin/sh";
722 if (!(vis->shell = strdup(shell)))
723 goto err;
724 vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL];
725 vis->event = event;
726 if (event) {
727 if (event->mode_insert_input)
728 vis_modes[VIS_MODE_INSERT].input = event->mode_insert_input;
729 if (event->mode_replace_input)
730 vis_modes[VIS_MODE_REPLACE].input = event->mode_replace_input;
732 return vis;
733 err:
734 vis_free(vis);
735 return NULL;
738 void vis_free(Vis *vis) {
739 if (!vis)
740 return;
741 vis_event_emit(vis, VIS_EVENT_QUIT);
742 vis->event = NULL;
743 while (vis->windows)
744 vis_window_close(vis->windows);
745 file_free(vis, vis->command_file);
746 file_free(vis, vis->search_file);
747 file_free(vis, vis->error_file);
748 for (int i = 0; i < LENGTH(vis->registers); i++)
749 register_release(&vis->registers[i]);
750 vis->ui->free(vis->ui);
751 if (vis->usercmds) {
752 const char *name;
753 while (map_first(vis->usercmds, &name) && vis_cmd_unregister(vis, name));
755 map_free(vis->usercmds);
756 map_free(vis->cmds);
757 map_free(vis->options);
758 map_free(vis->actions);
759 map_free(vis->keymap);
760 buffer_release(&vis->input_queue);
761 for (int i = 0; i < VIS_MODE_INVALID; i++)
762 map_free(vis_modes[i].bindings);
763 array_release_full(&vis->motions);
764 array_release_full(&vis->textobjects);
765 while (array_length(&vis->bindings))
766 vis_binding_free(vis, array_get_ptr(&vis->bindings, 0));
767 array_release(&vis->bindings);
768 while (array_length(&vis->actions_user))
769 vis_action_free(vis, array_get_ptr(&vis->actions_user, 0));
770 array_release(&vis->actions_user);
771 free(vis->shell);
772 free(vis);
775 void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) {
776 text_insert(vis->win->file->text, pos, data, len);
777 windows_invalidate(vis, pos, pos + len);
780 void vis_insert_key(Vis *vis, const char *data, size_t len) {
781 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
782 size_t pos = view_cursors_pos(c);
783 vis_insert(vis, pos, data, len);
784 view_cursors_scroll_to(c, pos + len);
788 void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) {
789 Text *txt = vis->win->file->text;
790 Iterator it = text_iterator_get(txt, pos);
791 int chars = text_char_count(data, len);
792 for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n'; )
793 text_iterator_char_next(&it, NULL);
795 text_delete(txt, pos, it.pos - pos);
796 vis_insert(vis, pos, data, len);
799 void vis_replace_key(Vis *vis, const char *data, size_t len) {
800 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
801 size_t pos = view_cursors_pos(c);
802 vis_replace(vis, pos, data, len);
803 view_cursors_scroll_to(c, pos + len);
807 void vis_delete(Vis *vis, size_t pos, size_t len) {
808 text_delete(vis->win->file->text, pos, len);
809 windows_invalidate(vis, pos, pos + len);
812 bool vis_action_register(Vis *vis, const KeyAction *action) {
813 return map_put(vis->actions, action->name, action);
816 bool vis_keymap_add(Vis *vis, const char *key, const char *mapping) {
817 return map_put(vis->keymap, key, mapping);
820 void vis_keymap_disable(Vis *vis) {
821 vis->keymap_disabled = true;
824 static void window_jumplist_add(Win *win, size_t pos) {
825 Mark mark = text_mark_set(win->file->text, pos);
826 if (mark && win->jumplist)
827 ringbuf_add(win->jumplist, (void*)mark);
830 static void window_jumplist_invalidate(Win *win) {
831 if (win->jumplist)
832 ringbuf_invalidate(win->jumplist);
835 void vis_do(Vis *vis) {
836 Win *win = vis->win;
837 File *file = win->file;
838 Text *txt = file->text;
839 View *view = win->view;
840 Action *a = &vis->action;
842 if (a->op == &vis_operators[VIS_OP_FILTER] && !vis->mode->visual)
843 vis_mode_switch(vis, VIS_MODE_VISUAL_LINE);
845 int count = MAX(a->count, 1);
846 if (a->op == &vis_operators[VIS_OP_MODESWITCH])
847 count = 1; /* count should apply to inserted text not motion */
848 bool repeatable = a->op && !vis->macro_operator && !vis->win->parent;
849 bool multiple_cursors = view_cursors_multiple(view);
850 bool linewise = !(a->type & CHARWISE) && (
851 a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) ||
852 vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]);
854 for (Cursor *cursor = view_cursors(view), *next; cursor; cursor = next) {
856 next = view_cursors_next(cursor);
857 size_t pos = view_cursors_pos(cursor);
858 Register *reg = multiple_cursors ? view_cursors_register(cursor) : a->reg;
859 if (!reg)
860 reg = &vis->registers[file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT];
862 OperatorContext c = {
863 .count = count,
864 .pos = pos,
865 .newpos = EPOS,
866 .range = text_range_empty(),
867 .reg = reg,
868 .linewise = linewise,
869 .arg = &a->arg,
872 bool err = false;
873 if (a->movement) {
874 size_t start = pos;
875 for (int i = 0; i < count; i++) {
876 size_t pos_prev = pos;
877 if (a->movement->txt)
878 pos = a->movement->txt(txt, pos);
879 else if (a->movement->cur)
880 pos = a->movement->cur(cursor);
881 else if (a->movement->file)
882 pos = a->movement->file(vis, file, pos);
883 else if (a->movement->vis)
884 pos = a->movement->vis(vis, txt, pos);
885 else if (a->movement->view)
886 pos = a->movement->view(vis, view);
887 else if (a->movement->win)
888 pos = a->movement->win(vis, win, pos);
889 else if (a->movement->user)
890 pos = a->movement->user(vis, win, a->movement->data, pos);
891 if (pos == EPOS || a->movement->type & IDEMPOTENT || pos == pos_prev) {
892 err = a->movement->type & COUNT_EXACT;
893 break;
897 if (err) {
898 repeatable = false;
899 continue; // break?
902 if (pos == EPOS) {
903 c.range.start = start;
904 c.range.end = start;
905 pos = start;
906 } else {
907 c.range = text_range_new(start, pos);
908 c.newpos = pos;
911 if (!a->op) {
912 if (a->movement->type & CHARWISE)
913 view_cursors_scroll_to(cursor, pos);
914 else
915 view_cursors_to(cursor, pos);
916 if (vis->mode->visual)
917 c.range = view_cursors_selection_get(cursor);
918 if (a->movement->type & JUMP)
919 window_jumplist_add(win, pos);
920 else
921 window_jumplist_invalidate(win);
922 } else if (a->movement->type & INCLUSIVE && c.range.end > start) {
923 c.range.end = text_char_next(txt, c.range.end);
924 } else if (linewise && (a->movement->type & LINEWISE_INCLUSIVE)) {
925 c.range.end = text_char_next(txt, c.range.end);
927 } else if (a->textobj) {
928 if (vis->mode->visual)
929 c.range = view_cursors_selection_get(cursor);
930 else
931 c.range.start = c.range.end = pos;
932 for (int i = 0; i < count; i++) {
933 Filerange r = text_range_empty();
934 if (a->textobj->txt)
935 r = a->textobj->txt(txt, pos);
936 else if (a->textobj->vis)
937 r = a->textobj->vis(vis, txt, pos);
938 else if (a->textobj->user)
939 r = a->textobj->user(vis, win, a->textobj->data, pos);
940 if (!text_range_valid(&r))
941 break;
942 if (a->textobj->type & TEXTOBJECT_DELIMITED_OUTER) {
943 r.start--;
944 r.end++;
947 if (vis->mode->visual || (i > 0 && !(a->textobj->type & TEXTOBJECT_NON_CONTIGUOUS)))
948 c.range = text_range_union(&c.range, &r);
949 else
950 c.range = r;
952 if (i < count - 1) {
953 if (a->textobj->type & TEXTOBJECT_EXTEND_BACKWARD) {
954 pos = c.range.start;
955 if ((a->textobj->type & TEXTOBJECT_DELIMITED_INNER) && pos > 0)
956 pos--;
957 } else {
958 pos = c.range.end;
959 if (a->textobj->type & TEXTOBJECT_DELIMITED_INNER)
960 pos++;
964 } else if (vis->mode->visual) {
965 c.range = view_cursors_selection_get(cursor);
966 if (!text_range_valid(&c.range))
967 c.range.start = c.range.end = pos;
970 if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL])
971 c.range = text_range_linewise(txt, &c.range);
972 if (vis->mode->visual) {
973 view_cursors_selection_set(cursor, &c.range);
974 if (vis->mode == &vis_modes[VIS_MODE_VISUAL] || a->textobj)
975 view_cursors_selection_sync(cursor);
978 if (a->op) {
979 size_t pos = a->op->func(vis, txt, &c);
980 if (pos == EPOS) {
981 view_cursors_dispose(cursor);
982 } else if (pos <= text_size(txt)) {
983 /* moving the cursor will affect the selection.
984 * because we want to be able to later restore
985 * the old selection we update it again before
986 * leaving visual mode.
988 Filerange sel = view_cursors_selection_get(cursor);
989 view_cursors_to(cursor, pos);
990 if (vis->mode->visual) {
991 if (sel.start == EPOS && sel.end == EPOS)
992 sel = c.range;
993 else if (sel.start == EPOS)
994 sel = text_range_new(c.range.start, sel.end);
995 else if (sel.end == EPOS)
996 sel = text_range_new(c.range.start, sel.start);
997 if (vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE])
998 sel = text_range_linewise(txt, &sel);
999 if (!text_range_contains(&sel, pos)) {
1000 Filerange cur = text_range_new(pos, pos);
1001 sel = text_range_union(&sel, &cur);
1003 view_cursors_selection_set(cursor, &sel);
1009 if (a->op) {
1010 /* we do not support visual repeat, still do something resonable */
1011 if (vis->mode->visual && !a->movement && !a->textobj)
1012 a->movement = &vis_motions[VIS_MOVE_NOP];
1014 /* operator implementations must not change the mode,
1015 * they might get called multiple times (once for every cursor)
1017 if (a->op == &vis_operators[VIS_OP_CHANGE]) {
1018 vis_mode_switch(vis, VIS_MODE_INSERT);
1019 } else if (a->op == &vis_operators[VIS_OP_MODESWITCH]) {
1020 vis_mode_switch(vis, a->mode);
1021 } else if (a->op == &vis_operators[VIS_OP_FILTER]) {
1022 if (a->arg.s)
1023 vis_cmd(vis, a->arg.s);
1024 else
1025 vis_prompt_show(vis, ":|");
1026 } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) {
1027 mode_set(vis, vis->mode_prev);
1028 } else if (vis->mode->visual) {
1029 vis_mode_switch(vis, VIS_MODE_NORMAL);
1032 if (vis->mode == &vis_modes[VIS_MODE_NORMAL])
1033 vis_file_snapshot(vis, file);
1034 vis_draw(vis);
1037 if (a != &vis->action_prev) {
1038 if (repeatable) {
1039 if (!a->macro)
1040 a->macro = vis->macro_operator;
1041 vis->action_prev = *a;
1043 action_reset(a);
1047 void action_reset(Action *a) {
1048 memset(a, 0, sizeof(*a));
1049 a->count = VIS_COUNT_UNKNOWN;
1052 void vis_cancel(Vis *vis) {
1053 action_reset(&vis->action);
1056 void vis_die(Vis *vis, const char *msg, ...) {
1057 va_list ap;
1058 va_start(ap, msg);
1059 vis->ui->die(vis->ui, msg, ap);
1060 va_end(ap);
1063 const char *vis_keys_next(Vis *vis, const char *keys) {
1064 if (!keys || !*keys)
1065 return NULL;
1066 TermKeyKey key;
1067 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1068 const char *next = NULL;
1069 /* first try to parse a special key of the form <Key> */
1070 if (*keys == '<' && keys[1] && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1071 return next+1;
1072 if (strncmp(keys, "<vis-", 5) == 0) {
1073 const char *start = keys + 1, *end = start;
1074 while (*end && *end != '>')
1075 end++;
1076 if (end > start && end - start - 1 < VIS_KEY_LENGTH_MAX && *end == '>') {
1077 char key[VIS_KEY_LENGTH_MAX];
1078 memcpy(key, start, end - start);
1079 key[end - start] = '\0';
1080 if (map_get(vis->actions, key))
1081 return end + 1;
1084 if (ISUTF8(*keys))
1085 keys++;
1086 while (!ISUTF8(*keys))
1087 keys++;
1088 return keys;
1091 long vis_keys_codepoint(Vis *vis, const char *keys) {
1092 long codepoint = -1;
1093 const char *next;
1094 TermKeyKey key;
1095 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1097 if (!keys[0])
1098 return -1;
1099 if (keys[0] == '<' && !keys[1])
1100 return '<';
1102 if (keys[0] == '<' && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1103 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1104 else if ((next = termkey_strpkey(termkey, keys, &key, TERMKEY_FORMAT_VIM)))
1105 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1107 if (codepoint != -1) {
1108 if (key.modifiers == TERMKEY_KEYMOD_CTRL)
1109 codepoint &= 0x1f;
1110 return codepoint;
1113 if (!next || key.type != TERMKEY_TYPE_KEYSYM)
1114 return -1;
1116 const int keysym[] = {
1117 TERMKEY_SYM_ENTER, '\n',
1118 TERMKEY_SYM_TAB, '\t',
1119 TERMKEY_SYM_BACKSPACE, '\b',
1120 TERMKEY_SYM_ESCAPE, 0x1b,
1121 TERMKEY_SYM_DELETE, 0x7f,
1125 for (const int *k = keysym; k[0]; k += 2) {
1126 if (key.code.sym == k[0])
1127 return k[1];
1130 return -1;
1133 bool vis_keys_utf8(Vis *vis, const char *keys, char utf8[static UTFmax+1]) {
1134 Rune rune = vis_keys_codepoint(vis, keys);
1135 if (rune == (Rune)-1)
1136 return false;
1137 size_t len = runetochar(utf8, &rune);
1138 utf8[len] = '\0';
1139 return true;
1142 static void vis_keys_process(Vis *vis, size_t pos) {
1143 Buffer *buf = &vis->input_queue;
1144 char *keys = buf->data + pos, *start = keys, *cur = keys, *end = keys, *binding_end = keys;;
1145 bool prefix = false;
1146 KeyBinding *binding = NULL;
1148 while (cur && *cur) {
1150 if (!(end = (char*)vis_keys_next(vis, cur))) {
1151 buffer_remove(buf, keys - buf->data, strlen(keys));
1152 return;
1155 char tmp = *end;
1156 *end = '\0';
1157 prefix = false;
1159 for (Mode *global_mode = vis->mode; global_mode && !prefix; global_mode = global_mode->parent) {
1160 for (int global = 0; global < 2 && !prefix; global++) {
1161 Mode *mode = (global || !vis->win) ?
1162 global_mode :
1163 &vis->win->modes[global_mode->id];
1164 if (!mode->bindings)
1165 continue;
1166 /* keep track of longest matching binding */
1167 KeyBinding *match = map_get(mode->bindings, start);
1168 if (match && end > binding_end) {
1169 binding = match;
1170 binding_end = end;
1172 /* "<" is never treated as a prefix because it
1173 * is used to denote special key symbols */
1174 if (strcmp(start, "<")) {
1175 prefix = (!match && map_contains(mode->bindings, start)) ||
1176 (match && !map_leaf(mode->bindings, start));
1181 *end = tmp;
1183 if (prefix) {
1184 /* input sofar is ambigious, wait for more */
1185 cur = end;
1186 end = start;
1187 } else if (binding) { /* exact match */
1188 if (binding->action) {
1189 size_t len = binding_end - start;
1190 strcpy(vis->key_prev, vis->key_current);
1191 strncpy(vis->key_current, start, len);
1192 vis->key_current[len] = '\0';
1193 end = (char*)binding->action->func(vis, binding_end, &binding->action->arg);
1194 if (!end) {
1195 end = start;
1196 break;
1198 start = cur = end;
1199 } else if (binding->alias) {
1200 buffer_remove(buf, start - buf->data, binding_end - start);
1201 buffer_insert0(buf, start - buf->data, binding->alias);
1202 cur = end = start;
1204 binding = NULL;
1205 binding_end = start;
1206 } else { /* no keybinding */
1207 KeyAction *action = NULL;
1208 if (start[0] == '<' && end[-1] == '>') {
1209 /* test for special editor key command */
1210 char tmp = end[-1];
1211 end[-1] = '\0';
1212 action = map_get(vis->actions, start+1);
1213 end[-1] = tmp;
1214 if (action) {
1215 size_t len = end - start;
1216 strcpy(vis->key_prev, vis->key_current);
1217 strncpy(vis->key_current, start, len);
1218 vis->key_current[len] = '\0';
1219 end = (char*)action->func(vis, end, &action->arg);
1220 if (!end) {
1221 end = start;
1222 break;
1226 if (!action && vis->mode->input) {
1227 end = (char*)vis_keys_next(vis, start);
1228 vis->mode->input(vis, start, end - start);
1230 start = cur = end;
1234 buffer_remove(buf, keys - buf->data, end - keys);
1237 void vis_keys_feed(Vis *vis, const char *input) {
1238 if (!input)
1239 return;
1240 Macro macro;
1241 macro_init(&macro);
1242 if (!macro_append(&macro, input))
1243 return;
1244 /* use internal function, to keep Lua based tests which use undo points working */
1245 macro_replay_internal(vis, &macro);
1246 macro_release(&macro);
1249 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record) {
1250 if (!input)
1251 return;
1252 if (record && vis->recording)
1253 macro_append(vis->recording, input);
1254 if (vis->macro_operator)
1255 macro_append(vis->macro_operator, input);
1256 if (buffer_append0(&vis->input_queue, input))
1257 vis_keys_process(vis, pos);
1260 static const char *getkey(Vis *vis) {
1261 TermKeyKey key = { 0 };
1262 if (!vis->ui->getkey(vis->ui, &key))
1263 return NULL;
1264 vis_info_hide(vis);
1265 bool use_keymap = vis->mode->id != VIS_MODE_INSERT &&
1266 vis->mode->id != VIS_MODE_REPLACE &&
1267 !vis->keymap_disabled;
1268 vis->keymap_disabled = false;
1269 if (key.type == TERMKEY_TYPE_UNICODE && use_keymap) {
1270 const char *mapped = map_get(vis->keymap, key.utf8);
1271 if (mapped) {
1272 size_t len = strlen(mapped)+1;
1273 if (len <= sizeof(key.utf8))
1274 memcpy(key.utf8, mapped, len);
1278 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1279 termkey_strfkey(termkey, vis->key, sizeof(vis->key), &key, TERMKEY_FORMAT_VIM);
1280 return vis->key;
1283 bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const void *context) {
1284 switch (signum) {
1285 case SIGBUS:
1286 for (File *file = vis->files; file; file = file->next) {
1287 if (text_sigbus(file->text, siginfo->si_addr))
1288 file->truncated = true;
1290 vis->sigbus = true;
1291 if (vis->running)
1292 siglongjmp(vis->sigbus_jmpbuf, 1);
1293 return true;
1294 case SIGINT:
1295 vis->cancel_filter = true;
1296 return true;
1297 case SIGCONT:
1298 vis->resume = true;
1299 /* fall through */
1300 case SIGWINCH:
1301 vis->need_resize = true;
1302 return true;
1303 case SIGTERM:
1304 case SIGHUP:
1305 vis->terminate = true;
1306 return true;
1308 return false;
1311 int vis_run(Vis *vis, int argc, char *argv[]) {
1312 if (!vis->windows)
1313 return EXIT_SUCCESS;
1314 if (vis->exit_status != -1)
1315 return vis->exit_status;
1316 vis->running = true;
1318 vis_event_emit(vis, VIS_EVENT_START);
1320 struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL;
1322 sigset_t emptyset;
1323 sigemptyset(&emptyset);
1324 vis_draw(vis);
1325 vis->exit_status = EXIT_SUCCESS;
1327 sigsetjmp(vis->sigbus_jmpbuf, 1);
1329 while (vis->running) {
1330 fd_set fds;
1331 FD_ZERO(&fds);
1332 FD_SET(STDIN_FILENO, &fds);
1334 if (vis->sigbus) {
1335 char *name = NULL;
1336 for (Win *next, *win = vis->windows; win; win = next) {
1337 next = win->next;
1338 if (win->file->truncated) {
1339 free(name);
1340 name = strdup(win->file->name);
1341 vis_window_close(win);
1344 if (!vis->windows)
1345 vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1346 else
1347 vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1348 vis->sigbus = false;
1349 free(name);
1352 if (vis->terminate)
1353 vis_die(vis, "Killed by SIGTERM\n");
1355 if (vis->resume) {
1356 vis->ui->resume(vis->ui);
1357 vis->resume = false;
1359 if (vis->need_resize) {
1360 vis->ui->resize(vis->ui);
1361 vis->need_resize = false;
1364 vis_update(vis);
1365 idle.tv_sec = vis->mode->idle_timeout;
1366 int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
1367 if (r == -1 && errno == EINTR)
1368 continue;
1370 if (r < 0) {
1371 /* TODO save all pending changes to a ~suffixed file */
1372 vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
1375 if (!FD_ISSET(STDIN_FILENO, &fds)) {
1376 if (vis->mode->idle)
1377 vis->mode->idle(vis);
1378 timeout = NULL;
1379 continue;
1382 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1383 termkey_advisereadable(termkey);
1384 const char *key;
1386 while ((key = getkey(vis)))
1387 vis_keys_push(vis, key, 0, true);
1389 if (vis->mode->idle)
1390 timeout = &idle;
1392 return vis->exit_status;
1395 static Macro *macro_get(Vis *vis, enum VisRegister id) {
1396 if (id == VIS_MACRO_LAST_RECORDED)
1397 return vis->last_recording;
1398 if (VIS_REG_A <= id && id <= VIS_REG_Z)
1399 id -= VIS_REG_A;
1400 if (id < LENGTH(vis->registers))
1401 return &vis->registers[id].buf;
1402 return NULL;
1405 void macro_operator_record(Vis *vis) {
1406 if (vis->macro_operator)
1407 return;
1408 vis->macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
1409 macro_reset(vis->macro_operator);
1412 void macro_operator_stop(Vis *vis) {
1413 if (!vis->macro_operator)
1414 return;
1415 Macro *dot = macro_get(vis, VIS_REG_DOT);
1416 buffer_put(dot, vis->macro_operator->data, vis->macro_operator->len);
1417 vis->action_prev.macro = dot;
1418 vis->macro_operator = NULL;
1421 bool vis_macro_record(Vis *vis, enum VisRegister id) {
1422 Macro *macro = macro_get(vis, id);
1423 if (vis->recording || !macro)
1424 return false;
1425 if (!(VIS_REG_A <= id && id <= VIS_REG_Z))
1426 macro_reset(macro);
1427 vis->recording = macro;
1428 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1429 return true;
1432 bool vis_macro_record_stop(Vis *vis) {
1433 if (!vis->recording)
1434 return false;
1435 /* XXX: hack to remove last recorded key, otherwise upon replay
1436 * we would start another recording */
1437 if (vis->recording->len > 1) {
1438 vis->recording->len--;
1439 vis->recording->data[vis->recording->len-1] = '\0';
1441 vis->last_recording = vis->recording;
1442 vis->recording = NULL;
1443 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1444 return true;
1447 bool vis_macro_recording(Vis *vis) {
1448 return vis->recording;
1451 static void macro_replay(Vis *vis, const Macro *macro) {
1452 const Macro *replaying = vis->replaying;
1453 vis->replaying = macro;
1454 macro_replay_internal(vis, macro);
1455 vis->replaying = replaying;
1458 static void macro_replay_internal(Vis *vis, const Macro *macro) {
1459 size_t pos = buffer_length0(&vis->input_queue);
1460 for (char *key = macro->data, *next; key; key = next) {
1461 char tmp;
1462 next = (char*)vis_keys_next(vis, key);
1463 if (next) {
1464 tmp = *next;
1465 *next = '\0';
1468 vis_keys_push(vis, key, pos, false);
1470 if (next)
1471 *next = tmp;
1475 bool vis_macro_replay(Vis *vis, enum VisRegister id) {
1476 if (id == VIS_REG_SEARCH)
1477 return vis_motion(vis, VIS_MOVE_SEARCH_NEXT);
1478 if (id == VIS_REG_COMMAND) {
1479 const char *cmd = register_get(vis, &vis->registers[id], NULL);
1480 return vis_cmd(vis, cmd);
1483 Macro *macro = macro_get(vis, id);
1484 if (!macro || macro == vis->recording)
1485 return false;
1486 int count = vis_count_get_default(vis, 1);
1487 vis_cancel(vis);
1488 for (int i = 0; i < count; i++)
1489 macro_replay(vis, macro);
1490 vis_file_snapshot(vis, vis->win->file);
1491 return true;
1494 void vis_repeat(Vis *vis) {
1495 const Macro *macro = vis->action_prev.macro;
1496 int count = vis->action.count;
1497 if (count != VIS_COUNT_UNKNOWN)
1498 vis->action_prev.count = count;
1499 else
1500 count = vis->action_prev.count;
1501 vis->action = vis->action_prev;
1502 vis_do(vis);
1503 if (macro) {
1504 Mode *mode = vis->mode;
1505 Action action_prev = vis->action_prev;
1506 if (count < 1 ||
1507 action_prev.op == &vis_operators[VIS_OP_CHANGE] ||
1508 action_prev.op == &vis_operators[VIS_OP_FILTER])
1509 count = 1;
1510 if (vis->action_prev.op == &vis_operators[VIS_OP_MODESWITCH])
1511 vis->action_prev.count = 1;
1512 for (int i = 0; i < count; i++) {
1513 mode_set(vis, mode);
1514 macro_replay(vis, macro);
1516 vis->action_prev = action_prev;
1518 vis_cancel(vis);
1519 vis_file_snapshot(vis, vis->win->file);
1522 enum VisMark vis_mark_from(Vis *vis, char mark) {
1523 if (mark >= 'a' && mark <= 'z')
1524 return VIS_MARK_a + mark - 'a';
1525 for (size_t i = 0; i < LENGTH(vis_marks); i++) {
1526 if (vis_marks[i].name == mark)
1527 return i;
1529 return VIS_MARK_INVALID;
1532 void vis_mark_set(Vis *vis, enum VisMark mark, size_t pos) {
1533 File *file = vis->win->file;
1534 if (mark < LENGTH(file->marks))
1535 file->marks[mark] = text_mark_set(file->text, pos);
1538 int vis_count_get(Vis *vis) {
1539 return vis->action.count;
1542 int vis_count_get_default(Vis *vis, int def) {
1543 if (vis->action.count == VIS_COUNT_UNKNOWN)
1544 return def;
1545 return vis->action.count;
1548 void vis_count_set(Vis *vis, int count) {
1549 vis->action.count = (count >= 0 ? count : VIS_COUNT_UNKNOWN);
1552 enum VisRegister vis_register_from(Vis *vis, char reg) {
1553 switch (reg) {
1554 case '+': return VIS_REG_CLIPBOARD;
1555 case '@': return VIS_MACRO_LAST_RECORDED;
1558 if ('a' <= reg && reg <= 'z')
1559 return VIS_REG_a + reg - 'a';
1560 if ('A' <= reg && reg <= 'Z')
1561 return VIS_REG_A + reg - 'A';
1562 for (size_t i = 0; i < LENGTH(vis_registers); i++) {
1563 if (vis_registers[i].name == reg)
1564 return i;
1566 return VIS_REG_INVALID;
1569 void vis_register_set(Vis *vis, enum VisRegister reg) {
1570 if (VIS_REG_A <= reg && reg <= VIS_REG_Z) {
1571 vis->action.reg = &vis->registers[VIS_REG_a + reg - VIS_REG_A];
1572 vis->action.reg->append = true;
1573 } else if (reg < LENGTH(vis->registers)) {
1574 vis->action.reg = &vis->registers[reg];
1575 vis->action.reg->append = false;
1579 const char *vis_register_get(Vis *vis, enum VisRegister reg, size_t *len) {
1580 if (VIS_REG_A <= reg && reg <= VIS_REG_Z)
1581 reg = VIS_REG_a + reg - VIS_REG_A;
1582 if (reg < LENGTH(vis->registers))
1583 return register_get(vis, &vis->registers[reg], len);
1584 *len = 0;
1585 return NULL;
1588 void vis_exit(Vis *vis, int status) {
1589 vis->running = false;
1590 vis->exit_status = status;
1593 void vis_insert_tab(Vis *vis) {
1594 if (!vis->expandtab) {
1595 vis_insert_key(vis, "\t", 1);
1596 return;
1598 char spaces[9];
1599 int tabwidth = MIN(vis->tabwidth, LENGTH(spaces) - 1);
1600 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
1601 size_t pos = view_cursors_pos(c);
1602 int width = text_line_width_get(vis->win->file->text, pos);
1603 int count = tabwidth - (width % tabwidth);
1604 for (int i = 0; i < count; i++)
1605 spaces[i] = ' ';
1606 spaces[count] = '\0';
1607 vis_insert(vis, pos, spaces, count);
1608 view_cursors_scroll_to(c, pos + count);
1612 size_t vis_text_insert_nl(Vis *vis, Text *txt, size_t pos) {
1613 const char *nl = text_newline_char(txt);
1614 size_t nl_len = strlen(nl), indent_len = 0;
1615 char byte, *indent = NULL;
1616 /* insert second newline at end of file, except if there is already one */
1617 bool eof = pos == text_size(txt);
1618 bool nl2 = eof && !(pos > 0 && text_byte_get(txt, pos-1, &byte) && byte == '\n');
1620 if (vis->autoindent) {
1621 /* copy leading white space of current line */
1622 size_t begin = text_line_begin(txt, pos);
1623 size_t start = text_line_start(txt, begin);
1624 size_t end = text_line_end(txt, start);
1625 if (start > pos)
1626 start = pos;
1627 indent_len = start >= begin ? start-begin : 0;
1628 if (start == end) {
1629 pos = begin;
1630 } else {
1631 indent = malloc(indent_len+1);
1632 if (indent)
1633 indent_len = text_bytes_get(txt, begin, indent_len, indent);
1637 text_insert(txt, pos, nl, nl_len);
1638 if (eof) {
1639 if (nl2)
1640 text_insert(txt, pos, nl, nl_len);
1641 else
1642 pos -= nl_len; /* place cursor before, not after nl */
1644 pos += nl_len;
1646 if (indent)
1647 text_insert(txt, pos, indent, indent_len);
1648 free(indent);
1649 return pos + indent_len;
1652 void vis_insert_nl(Vis *vis) {
1653 View *view = vis->win->view;
1654 Text *txt = vis->win->file->text;
1655 for (Cursor *c = view_cursors(view); c; c = view_cursors_next(c)) {
1656 size_t pos = view_cursors_pos(c);
1657 size_t newpos = vis_text_insert_nl(vis, txt, pos);
1658 /* This is a bit of a hack to fix cursor positioning when
1659 * inserting a new line at the start of the view port.
1660 * It has the effect of reseting the mark used by the view
1661 * code to keep track of the start of the visible region.
1663 view_cursors_to(c, pos);
1664 view_cursors_to(c, newpos);
1666 size_t pos = view_cursor_get(view);
1667 windows_invalidate(vis, pos, pos-1);
1670 Regex *vis_regex(Vis *vis, const char *pattern) {
1671 if (!pattern && !(pattern = register_get(vis, &vis->registers[VIS_REG_SEARCH], NULL)))
1672 return NULL;
1673 Regex *regex = text_regex_new();
1674 if (!regex)
1675 return NULL;
1676 if (text_regex_compile(regex, pattern, REG_EXTENDED|REG_NEWLINE) != 0) {
1677 text_regex_free(regex);
1678 return NULL;
1680 register_put0(vis, &vis->registers[VIS_REG_SEARCH], pattern);
1681 return regex;
1684 int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
1685 void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
1686 void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len)) {
1688 /* if an invalid range was given, stdin (i.e. key board input) is passed
1689 * through the external command. */
1690 Text *text = file->text;
1691 int pin[2], pout[2], perr[2], status = -1;
1692 bool interactive = !text_range_valid(range);
1693 Filerange rout = interactive ? text_range_new(0, 0) : *range;
1695 if (pipe(pin) == -1)
1696 return -1;
1697 if (pipe(pout) == -1) {
1698 close(pin[0]);
1699 close(pin[1]);
1700 return -1;
1703 if (pipe(perr) == -1) {
1704 close(pin[0]);
1705 close(pin[1]);
1706 close(pout[0]);
1707 close(pout[1]);
1708 return -1;
1711 vis->ui->terminal_save(vis->ui);
1712 pid_t pid = fork();
1714 if (pid == -1) {
1715 close(pin[0]);
1716 close(pin[1]);
1717 close(pout[0]);
1718 close(pout[1]);
1719 close(perr[0]);
1720 close(perr[1]);
1721 vis_info_show(vis, "fork failure: %s", strerror(errno));
1722 return -1;
1723 } else if (pid == 0) { /* child i.e filter */
1724 int null = open("/dev/null", O_WRONLY);
1725 if (null == -1) {
1726 fprintf(stderr, "failed to open /dev/null");
1727 exit(EXIT_FAILURE);
1729 if (!interactive)
1730 dup2(pin[0], STDIN_FILENO);
1731 close(pin[0]);
1732 close(pin[1]);
1733 if (interactive)
1734 dup2(STDERR_FILENO, STDOUT_FILENO);
1735 else if (read_stdout)
1736 dup2(pout[1], STDOUT_FILENO);
1737 else
1738 dup2(null, STDOUT_FILENO);
1739 close(pout[1]);
1740 close(pout[0]);
1741 if (!interactive) {
1742 if (read_stderr)
1743 dup2(perr[1], STDERR_FILENO);
1744 else
1745 dup2(null, STDERR_FILENO);
1747 close(perr[0]);
1748 close(perr[1]);
1749 close(null);
1751 if (file->name) {
1752 char *name = strrchr(file->name, '/');
1753 setenv("vis_filepath", file->name, 1);
1754 setenv("vis_filename", name ? name+1 : file->name, 1);
1757 if (!argv[1])
1758 execlp(vis->shell, vis->shell, "-c", argv[0], (char*)NULL);
1759 else
1760 execvp(argv[0], (char* const*)argv);
1761 fprintf(stderr, "exec failure: %s", strerror(errno));
1762 exit(EXIT_FAILURE);
1765 vis->cancel_filter = false;
1767 close(pin[0]);
1768 close(pout[1]);
1769 close(perr[1]);
1771 if (fcntl(pout[0], F_SETFL, O_NONBLOCK) == -1 ||
1772 fcntl(perr[0], F_SETFL, O_NONBLOCK) == -1)
1773 goto err;
1775 fd_set rfds, wfds;
1777 do {
1778 if (vis->cancel_filter) {
1779 kill(-pid, SIGTERM);
1780 break;
1783 FD_ZERO(&rfds);
1784 FD_ZERO(&wfds);
1785 if (pin[1] != -1)
1786 FD_SET(pin[1], &wfds);
1787 if (pout[0] != -1)
1788 FD_SET(pout[0], &rfds);
1789 if (perr[0] != -1)
1790 FD_SET(perr[0], &rfds);
1792 if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) {
1793 if (errno == EINTR)
1794 continue;
1795 vis_info_show(vis, "Select failure");
1796 break;
1799 if (pin[1] != -1 && FD_ISSET(pin[1], &wfds)) {
1800 Filerange junk = rout;
1801 if (junk.end > junk.start + PIPE_BUF)
1802 junk.end = junk.start + PIPE_BUF;
1803 ssize_t len = text_write_range(text, &junk, pin[1]);
1804 if (len > 0) {
1805 rout.start += len;
1806 if (text_range_size(&rout) == 0) {
1807 close(pout[1]);
1808 pout[1] = -1;
1810 } else {
1811 close(pin[1]);
1812 pin[1] = -1;
1813 if (len == -1)
1814 vis_info_show(vis, "Error writing to external command");
1818 if (pout[0] != -1 && FD_ISSET(pout[0], &rfds)) {
1819 char buf[BUFSIZ];
1820 ssize_t len = read(pout[0], buf, sizeof buf);
1821 if (len > 0) {
1822 if (read_stdout)
1823 (*read_stdout)(stdout_context, buf, len);
1824 } else if (len == 0) {
1825 close(pout[0]);
1826 pout[0] = -1;
1827 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1828 vis_info_show(vis, "Error reading from filter stdout");
1829 close(pout[0]);
1830 pout[0] = -1;
1834 if (perr[0] != -1 && FD_ISSET(perr[0], &rfds)) {
1835 char buf[BUFSIZ];
1836 ssize_t len = read(perr[0], buf, sizeof buf);
1837 if (len > 0) {
1838 if (read_stderr)
1839 (*read_stderr)(stderr_context, buf, len);
1840 } else if (len == 0) {
1841 close(perr[0]);
1842 perr[0] = -1;
1843 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1844 vis_info_show(vis, "Error reading from filter stderr");
1845 close(perr[0]);
1846 perr[0] = -1;
1850 } while (pin[1] != -1 || pout[0] != -1 || perr[0] != -1);
1852 err:
1853 if (pin[1] != -1)
1854 close(pin[1]);
1855 if (pout[0] != -1)
1856 close(pout[0]);
1857 if (perr[0] != -1)
1858 close(perr[0]);
1860 for (pid_t died; (died = waitpid(pid, &status, 0)) != -1 && pid != died;);
1862 vis->ui->terminal_restore(vis->ui);
1864 return status;
1867 static ssize_t read_buffer(void *context, char *data, size_t len) {
1868 buffer_append(context, data, len);
1869 return len;
1872 int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[], char **out, char **err) {
1873 Buffer bufout, buferr;
1874 buffer_init(&bufout);
1875 buffer_init(&buferr);
1876 int status = vis_pipe(vis, file, range, argv,
1877 &bufout, out ? read_buffer : NULL,
1878 &buferr, err ? read_buffer : NULL);
1879 buffer_terminate(&bufout);
1880 buffer_terminate(&buferr);
1881 if (out)
1882 *out = buffer_move(&bufout);
1883 if (err)
1884 *err = buffer_move(&buferr);
1885 buffer_release(&bufout);
1886 buffer_release(&buferr);
1887 return status;
1890 bool vis_cmd(Vis *vis, const char *cmdline) {
1891 if (!cmdline)
1892 return true;
1893 while (*cmdline == ':')
1894 cmdline++;
1895 size_t len = strlen(cmdline);
1896 char *line = malloc(len+2);
1897 if (!line)
1898 return false;
1899 strncpy(line, cmdline, len+1);
1901 for (char *end = line + len - 1; end >= line && isspace((unsigned char)*end); end--)
1902 *end = '\0';
1904 enum SamError err = sam_cmd(vis, line);
1905 if (err != SAM_ERR_OK)
1906 vis_info_show(vis, "%s", sam_error(err));
1907 free(line);
1908 return err == SAM_ERR_OK;
1911 void vis_file_snapshot(Vis *vis, File *file) {
1912 if (!vis->replaying)
1913 text_snapshot(file->text);
1916 Text *vis_text(Vis *vis) {
1917 return vis->win->file->text;
1920 View *vis_view(Vis *vis) {
1921 return vis->win->view;
1924 Win *vis_window(Vis *vis) {
1925 return vis->win;
1928 bool vis_get_autoindent(const Vis *vis) {
1929 return vis->autoindent;