vt100: do not crash if termkey is not yet initialized
[vis.git] / vis.c
blob768c3cb9d0bc4ae8266a8ab3cb24c36e51baf16c
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"
30 #include "ui.h"
33 static void macro_replay(Vis *vis, const Macro *macro);
34 static void macro_replay_internal(Vis *vis, const Macro *macro);
35 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record);
37 bool vis_event_emit(Vis *vis, enum VisEvents id, ...) {
38 if (!vis->event)
39 return true;
41 if (!vis->initialized) {
42 vis->initialized = true;
43 vis->ui->init(vis->ui, vis);
44 if (vis->event->init)
45 vis->event->init(vis);
48 va_list ap;
49 va_start(ap, id);
50 bool ret = true;
52 switch (id) {
53 case VIS_EVENT_INIT:
54 break;
55 case VIS_EVENT_START:
56 if (vis->event->start)
57 vis->event->start(vis);
58 break;
59 case VIS_EVENT_FILE_OPEN:
60 case VIS_EVENT_FILE_SAVE_PRE:
61 case VIS_EVENT_FILE_SAVE_POST:
62 case VIS_EVENT_FILE_CLOSE:
64 File *file = va_arg(ap, File*);
65 if (file->internal)
66 break;
67 if (id == VIS_EVENT_FILE_OPEN && vis->event->file_open) {
68 vis->event->file_open(vis, file);
69 } else if (id == VIS_EVENT_FILE_SAVE_PRE && vis->event->file_save_pre) {
70 const char *path = va_arg(ap, const char*);
71 ret = vis->event->file_save_pre(vis, file, path);
72 } else if (id == VIS_EVENT_FILE_SAVE_POST && vis->event->file_save_post) {
73 const char *path = va_arg(ap, const char*);
74 vis->event->file_save_post(vis, file, path);
75 } else if (id == VIS_EVENT_FILE_CLOSE && vis->event->file_close) {
76 vis->event->file_close(vis, file);
78 break;
80 case VIS_EVENT_WIN_OPEN:
81 case VIS_EVENT_WIN_CLOSE:
82 case VIS_EVENT_WIN_HIGHLIGHT:
83 case VIS_EVENT_WIN_STATUS:
85 Win *win = va_arg(ap, Win*);
86 if (win->file->internal && id != VIS_EVENT_WIN_STATUS)
87 break;
88 if (vis->event->win_open && id == VIS_EVENT_WIN_OPEN) {
89 vis->event->win_open(vis, win);
90 } else if (vis->event->win_close && id == VIS_EVENT_WIN_CLOSE) {
91 vis->event->win_close(vis, win);
92 } else if (vis->event->win_highlight && id == VIS_EVENT_WIN_HIGHLIGHT) {
93 vis->event->win_highlight(vis, win);
94 } else if (vis->event->win_status && id == VIS_EVENT_WIN_STATUS) {
95 vis->event->win_status(vis, win);
97 break;
99 case VIS_EVENT_QUIT:
100 if (vis->event->quit)
101 vis->event->quit(vis);
102 break;
105 va_end(ap);
106 return ret;
109 /** window / file handling */
111 static void file_free(Vis *vis, File *file) {
112 if (!file)
113 return;
114 if (file->refcount > 1) {
115 --file->refcount;
116 return;
118 vis_event_emit(vis, VIS_EVENT_FILE_CLOSE, file);
119 for (size_t i = 0; i < LENGTH(file->marks); i++)
120 mark_release(&file->marks[i]);
121 text_free(file->text);
122 free((char*)file->name);
124 if (file->prev)
125 file->prev->next = file->next;
126 if (file->next)
127 file->next->prev = file->prev;
128 if (vis->files == file)
129 vis->files = file->next;
130 free(file);
133 static File *file_new_text(Vis *vis, Text *text) {
134 File *file = calloc(1, sizeof(*file));
135 if (!file)
136 return NULL;
137 file->fd = -1;
138 file->text = text;
139 file->stat = text_stat(text);
140 for (size_t i = 0; i < LENGTH(file->marks); i++)
141 mark_init(&file->marks[i]);
142 if (vis->files)
143 vis->files->prev = file;
144 file->next = vis->files;
145 vis->files = file;
146 return file;
149 char *absolute_path(const char *name) {
150 if (!name)
151 return NULL;
152 char *copy1 = strdup(name);
153 char *copy2 = strdup(name);
154 char *path_absolute = NULL;
155 char path_normalized[PATH_MAX] = "";
157 if (!copy1 || !copy2)
158 goto err;
160 char *dir = dirname(copy1);
161 char *base = basename(copy2);
162 if (!(path_absolute = realpath(dir, NULL)))
163 goto err;
164 if (strcmp(path_absolute, "/") == 0)
165 path_absolute[0] = '\0';
167 snprintf(path_normalized, sizeof(path_normalized), "%s/%s",
168 path_absolute, base);
169 err:
170 free(copy1);
171 free(copy2);
172 free(path_absolute);
173 return path_normalized[0] ? strdup(path_normalized) : NULL;
176 static File *file_new(Vis *vis, const char *name) {
177 char *name_absolute = NULL;
178 if (name) {
179 if (!(name_absolute = absolute_path(name)))
180 return NULL;
181 File *existing = NULL;
182 /* try to detect whether the same file is already open in another window
183 * TODO: do this based on inodes */
184 for (File *file = vis->files; file; file = file->next) {
185 if (file->name && strcmp(file->name, name_absolute) == 0) {
186 existing = file;
187 break;
190 if (existing) {
191 free(name_absolute);
192 return existing;
196 File *file = NULL;
197 Text *text = text_load_method(name, vis->load_method);
198 if (!text && name && errno == ENOENT)
199 text = text_load(NULL);
200 if (!text)
201 goto err;
202 if (!(file = file_new_text(vis, text)))
203 goto err;
204 file->name = name_absolute;
205 vis_event_emit(vis, VIS_EVENT_FILE_OPEN, file);
206 return file;
207 err:
208 free(name_absolute);
209 text_free(text);
210 file_free(vis, file);
211 return NULL;
214 static File *file_new_internal(Vis *vis, const char *filename) {
215 File *file = file_new(vis, filename);
216 if (file) {
217 file->refcount = 1;
218 file->internal = true;
220 return file;
223 void file_name_set(File *file, const char *name) {
224 if (name == file->name)
225 return;
226 free((char*)file->name);
227 file->name = absolute_path(name);
230 const char *file_name_get(File *file) {
231 /* TODO: calculate path relative to working directory, cache result */
232 if (!file->name)
233 return NULL;
234 char cwd[PATH_MAX];
235 if (!getcwd(cwd, sizeof cwd))
236 return file->name;
237 const char *path = strstr(file->name, cwd);
238 if (path != file->name)
239 return file->name;
240 size_t cwdlen = strlen(cwd);
241 return file->name[cwdlen] == '/' ? file->name+cwdlen+1 : file->name;
244 void vis_window_status(Win *win, const char *status) {
245 win->ui->status(win->ui, status);
248 void window_selection_save(Win *win) {
249 Vis *vis = win->vis;
250 View *view = win->view;
251 Array sel = view_selections_get_all(view);
252 vis_mark_set(win, VIS_MARK_SELECTION, &sel);
253 array_release(&sel);
254 vis_jumplist_save(vis);
258 static void window_free(Win *win) {
259 if (!win)
260 return;
261 Vis *vis = win->vis;
262 for (Win *other = vis->windows; other; other = other->next) {
263 if (other->parent == win)
264 other->parent = NULL;
266 if (vis->ui)
267 vis->ui->window_free(win->ui);
268 view_free(win->view);
269 for (size_t i = 0; i < LENGTH(win->modes); i++)
270 map_free(win->modes[i].bindings);
271 marklist_release(&win->jumplist);
272 mark_release(&win->saved_selections);
273 free(win);
276 static void window_draw_colorcolumn(Win *win) {
277 View *view = win->view;
278 int cc = view_colorcolumn_get(view);
279 if (cc <= 0)
280 return;
281 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_COLOR_COLUMN);
282 size_t lineno = 0;
283 int line_cols = 0; /* Track the number of columns we've passed on each line */
284 bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */
285 int width = view_width_get(view);
287 for (Line *l = view_lines_first(view); l; l = l->next) {
288 if (l->lineno != lineno) {
289 line_cols = 0;
290 line_cc_set = false;
291 if (!(lineno = l->lineno))
292 break;
294 if (line_cc_set)
295 continue;
297 /* This screen line contains the cell we want to highlight */
298 if (cc <= line_cols + width) {
299 CellStyle *orig = &l->cells[cc - 1 - line_cols].style;
300 orig->attr = style.attr;
301 orig->fg = is_default_color(style.fg) ? orig->fg : style.fg;
302 orig->bg = is_default_color(style.bg) ? orig->bg : style.bg;
303 line_cc_set = true;
304 } else {
305 line_cols += width;
310 static void window_draw_cursorline(Win *win) {
311 Vis *vis = win->vis;
312 View *view = win->view;
313 enum UiOption options = view_options_get(view);
314 if (!(options & UI_OPTION_CURSOR_LINE))
315 return;
316 if (vis->mode->visual || vis->win != win)
317 return;
318 if (view_selections_count(view) > 1)
319 return;
321 int width = view_width_get(view);
322 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_CURSOR_LINE);
323 Selection *sel = view_selections_primary_get(view);
324 size_t lineno = view_cursors_line_get(sel)->lineno;
325 for (Line *l = view_lines_first(view); l; l = l->next) {
326 if (l->lineno == lineno) {
327 for (int x = 0; x < width; x++) {
328 l->cells[x].style.attr |= style.attr;
329 l->cells[x].style.bg = style.bg;
331 } else if (l->lineno > lineno) {
332 break;
337 static void window_draw_selection(View *view, Selection *cur, CellStyle *style) {
338 Filerange sel = view_selections_get(cur);
339 if (!text_range_valid(&sel))
340 return;
341 Line *start_line; int start_col;
342 Line *end_line; int end_col;
343 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
344 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
345 if (!start_line && !end_line)
346 return;
347 if (!start_line) {
348 start_line = view_lines_first(view);
349 start_col = 0;
351 if (!end_line) {
352 end_line = view_lines_last(view);
353 end_col = end_line->width;
355 for (Line *l = start_line; l != end_line->next; l = l->next) {
356 int col = (l == start_line) ? start_col : 0;
357 int end = (l == end_line) ? end_col : l->width;
358 while (col < end) {
359 if (cell_color_equal(l->cells[col].style.fg, style->bg)) {
360 CellStyle old = l->cells[col].style;
361 if (!cell_color_equal(old.fg, old.bg)) {
362 l->cells[col].style.fg = old.bg;
363 l->cells[col].style.bg = old.fg;
364 } else {
365 l->cells[col].style.attr = style->attr;
367 } else {
368 l->cells[col].style.bg = style->bg;
370 col++;
375 static void window_draw_cursor_matching(Win *win, Selection *cur, CellStyle *style) {
376 if (win->vis->mode->visual)
377 return;
378 Line *line_match; int col_match;
379 size_t pos = view_cursors_pos(cur);
380 Filerange limits = view_viewport_get(win->view);
381 size_t pos_match = text_bracket_match_symbol(win->file->text, pos, "(){}[]\"'`", &limits);
382 if (pos == pos_match)
383 return;
384 if (!view_coord_get(win->view, pos_match, &line_match, NULL, &col_match))
385 return;
386 if (cell_color_equal(line_match->cells[col_match].style.fg, style->fg)) {
387 CellStyle old = line_match->cells[col_match].style;
388 line_match->cells[col_match].style.fg = old.bg;
389 line_match->cells[col_match].style.bg = old.fg;
390 } else {
391 line_match->cells[col_match].style.bg = style->bg;
395 static void window_draw_cursor(Win *win, Selection *cur, CellStyle *style, CellStyle *sel_style) {
396 if (win->vis->win != win)
397 return;
398 Line *line = view_cursors_line_get(cur);
399 int col = view_cursors_cell_get(cur);
400 if (!line || col == -1)
401 return;
402 line->cells[col].style = *style;
403 window_draw_cursor_matching(win, cur, sel_style);
404 return;
407 static void window_draw_selections(Win *win) {
408 View *view = win->view;
409 Filerange viewport = view_viewport_get(view);
410 Selection *sel = view_selections_primary_get(view);
411 CellStyle style_cursor = win->ui->style_get(win->ui, UI_STYLE_CURSOR);
412 CellStyle style_cursor_primary = win->ui->style_get(win->ui, UI_STYLE_CURSOR_PRIMARY);
413 CellStyle style_selection = win->ui->style_get(win->ui, UI_STYLE_SELECTION);
414 for (Selection *s = view_selections_prev(sel); s; s = view_selections_prev(s)) {
415 window_draw_selection(win->view, s, &style_selection);
416 size_t pos = view_cursors_pos(s);
417 if (pos < viewport.start)
418 break;
419 window_draw_cursor(win, s, &style_cursor, &style_selection);
421 window_draw_selection(win->view, sel, &style_selection);
422 window_draw_cursor(win, sel, &style_cursor_primary, &style_selection);
423 for (Selection *s = view_selections_next(sel); s; s = view_selections_next(s)) {
424 window_draw_selection(win->view, s, &style_selection);
425 size_t pos = view_cursors_pos(s);
426 if (pos > viewport.end)
427 break;
428 window_draw_cursor(win, s, &style_cursor, &style_selection);
432 static void window_draw_eof(Win *win) {
433 View *view = win->view;
434 if (view_width_get(view) == 0)
435 return;
436 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_EOF);
437 for (Line *l = view_lines_last(view)->next; l; l = l->next) {
438 strncpy(l->cells[0].data, view_symbol_eof_get(view), sizeof(l->cells[0].data)-1);
439 l->cells[0].style = style;
443 void vis_window_draw(Win *win) {
444 if (!win->ui || !view_update(win->view))
445 return;
446 Vis *vis = win->vis;
447 vis_event_emit(vis, VIS_EVENT_WIN_HIGHLIGHT, win);
449 window_draw_colorcolumn(win);
450 window_draw_cursorline(win);
451 if (!vis->win || vis->win == win || vis->win->parent == win)
452 window_draw_selections(win);
453 window_draw_eof(win);
455 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, win);
459 void vis_window_invalidate(Win *win) {
460 for (Win *w = win->vis->windows; w; w = w->next) {
461 if (w->file == win->file)
462 view_draw(w->view);
466 Win *window_new_file(Vis *vis, File *file, enum UiOption options) {
467 Win *win = calloc(1, sizeof(Win));
468 if (!win)
469 return NULL;
470 win->vis = vis;
471 win->file = file;
472 win->view = view_new(file->text);
473 win->ui = vis->ui->window_new(vis->ui, win, options);
474 if (!win->view || !win->ui) {
475 window_free(win);
476 return NULL;
478 marklist_init(&win->jumplist, 32);
479 mark_init(&win->saved_selections);
480 file->refcount++;
481 view_options_set(win->view, view_options_get(win->view));
482 view_tabwidth_set(win->view, vis->tabwidth);
484 if (vis->windows)
485 vis->windows->prev = win;
486 win->next = vis->windows;
487 vis->windows = win;
488 vis->win = win;
489 vis->ui->window_focus(win->ui);
490 for (size_t i = 0; i < LENGTH(win->modes); i++)
491 win->modes[i].parent = &vis_modes[i];
492 vis_event_emit(vis, VIS_EVENT_WIN_OPEN, win);
493 return win;
496 bool vis_window_reload(Win *win) {
497 const char *name = win->file->name;
498 if (!name)
499 return false; /* can't reload unsaved file */
500 /* temporarily unset file name, otherwise file_new returns the same File */
501 win->file->name = NULL;
502 File *file = file_new(win->vis, name);
503 win->file->name = name;
504 if (!file)
505 return false;
506 file_free(win->vis, win->file);
507 file->refcount = 1;
508 win->file = file;
509 view_reload(win->view, file->text);
510 return true;
513 bool vis_window_split(Win *original) {
514 Win *win = window_new_file(original->vis, original->file, UI_OPTION_STATUSBAR);
515 if (!win)
516 return false;
517 for (size_t i = 0; i < LENGTH(win->modes); i++) {
518 if (original->modes[i].bindings)
519 win->modes[i].bindings = map_new();
520 if (win->modes[i].bindings)
521 map_copy(win->modes[i].bindings, original->modes[i].bindings);
523 win->file = original->file;
524 view_options_set(win->view, view_options_get(original->view));
525 view_cursor_to(win->view, view_cursor_get(original->view));
526 return true;
529 void vis_window_focus(Win *win) {
530 if (!win)
531 return;
532 Vis *vis = win->vis;
533 vis->win = win;
534 vis->ui->window_focus(win->ui);
537 void vis_window_next(Vis *vis) {
538 Win *sel = vis->win;
539 if (!sel)
540 return;
541 vis_window_focus(sel->next ? sel->next : vis->windows);
544 void vis_window_prev(Vis *vis) {
545 Win *sel = vis->win;
546 if (!sel)
547 return;
548 sel = sel->prev;
549 if (!sel)
550 for (sel = vis->windows; sel->next; sel = sel->next);
551 vis_window_focus(sel);
554 int vis_window_width_get(const Win *win) {
555 return win->ui->window_width(win->ui);
558 int vis_window_height_get(const Win *win) {
559 return win->ui->window_height(win->ui);
562 void vis_draw(Vis *vis) {
563 for (Win *win = vis->windows; win; win = win->next)
564 view_draw(win->view);
567 void vis_redraw(Vis *vis) {
568 vis->ui->redraw(vis->ui);
569 vis_update(vis);
572 void vis_update(Vis *vis) {
573 vis->ui->draw(vis->ui);
576 void vis_suspend(Vis *vis) {
577 vis->ui->suspend(vis->ui);
580 void vis_resume(Vis *vis) {
581 vis->ui->resume(vis->ui);
584 bool vis_window_new(Vis *vis, const char *filename) {
585 File *file = file_new(vis, filename);
586 if (!file)
587 return false;
588 Win *win = window_new_file(vis, file, UI_OPTION_STATUSBAR|UI_OPTION_SYMBOL_EOF);
589 if (!win) {
590 file_free(vis, file);
591 return false;
594 return true;
597 bool vis_window_new_fd(Vis *vis, int fd) {
598 if (fd == -1)
599 return false;
600 if (!vis_window_new(vis, NULL))
601 return false;
602 vis->win->file->fd = fd;
603 return true;
606 bool vis_window_closable(Win *win) {
607 if (!win || !text_modified(win->file->text))
608 return true;
609 return win->file->refcount > 1;
612 void vis_window_swap(Win *a, Win *b) {
613 if (a == b || !a || !b)
614 return;
615 Vis *vis = a->vis;
616 Win *tmp = a->next;
617 a->next = b->next;
618 b->next = tmp;
619 if (a->next)
620 a->next->prev = a;
621 if (b->next)
622 b->next->prev = b;
623 tmp = a->prev;
624 a->prev = b->prev;
625 b->prev = tmp;
626 if (a->prev)
627 a->prev->next = a;
628 if (b->prev)
629 b->prev->next = b;
630 if (vis->windows == a)
631 vis->windows = b;
632 else if (vis->windows == b)
633 vis->windows = a;
634 vis->ui->window_swap(a->ui, b->ui);
635 if (vis->win == a)
636 vis_window_focus(b);
637 else if (vis->win == b)
638 vis_window_focus(a);
641 void vis_window_close(Win *win) {
642 if (!win)
643 return;
644 Vis *vis = win->vis;
645 vis_event_emit(vis, VIS_EVENT_WIN_CLOSE, win);
646 file_free(vis, win->file);
647 if (win->prev)
648 win->prev->next = win->next;
649 if (win->next)
650 win->next->prev = win->prev;
651 if (vis->windows == win)
652 vis->windows = win->next;
653 if (vis->win == win)
654 vis->win = win->next ? win->next : win->prev;
655 if (win == vis->message_window)
656 vis->message_window = NULL;
657 window_free(win);
658 if (vis->win)
659 vis->ui->window_focus(vis->win->ui);
660 vis_draw(vis);
663 Vis *vis_new(Ui *ui, VisEvent *event) {
664 if (!ui)
665 return NULL;
666 Vis *vis = calloc(1, sizeof(Vis));
667 if (!vis)
668 return NULL;
669 vis->exit_status = -1;
670 vis->ui = ui;
671 vis->tabwidth = 8;
672 vis->expandtab = false;
673 vis->change_colors = true;
674 for (size_t i = 0; i < LENGTH(vis->registers); i++)
675 register_init(&vis->registers[i]);
676 vis->registers[VIS_REG_BLACKHOLE].type = REGISTER_BLACKHOLE;
677 vis->registers[VIS_REG_CLIPBOARD].type = REGISTER_CLIPBOARD;
678 vis->registers[VIS_REG_NUMBER].type = REGISTER_NUMBER;
679 array_init(&vis->operators);
680 array_init(&vis->motions);
681 array_init(&vis->textobjects);
682 array_init(&vis->bindings);
683 array_init(&vis->actions_user);
684 action_reset(&vis->action);
685 buffer_init(&vis->input_queue);
686 if (!(vis->command_file = file_new_internal(vis, NULL)))
687 goto err;
688 if (!(vis->search_file = file_new_internal(vis, NULL)))
689 goto err;
690 if (!(vis->error_file = file_new_internal(vis, NULL)))
691 goto err;
692 if (!(vis->actions = map_new()))
693 goto err;
694 if (!(vis->keymap = map_new()))
695 goto err;
696 if (!sam_init(vis))
697 goto err;
698 struct passwd *pw;
699 char *shell = getenv("SHELL");
700 if ((!shell || !*shell) && (pw = getpwuid(getuid())))
701 shell = pw->pw_shell;
702 if (!shell || !*shell)
703 shell = "/bin/sh";
704 if (!(vis->shell = strdup(shell)))
705 goto err;
706 vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL];
707 vis->event = event;
708 if (event) {
709 if (event->mode_insert_input)
710 vis_modes[VIS_MODE_INSERT].input = event->mode_insert_input;
711 if (event->mode_replace_input)
712 vis_modes[VIS_MODE_REPLACE].input = event->mode_replace_input;
714 return vis;
715 err:
716 vis_free(vis);
717 return NULL;
720 void vis_free(Vis *vis) {
721 if (!vis)
722 return;
723 vis_event_emit(vis, VIS_EVENT_QUIT);
724 vis->event = NULL;
725 while (vis->windows)
726 vis_window_close(vis->windows);
727 file_free(vis, vis->command_file);
728 file_free(vis, vis->search_file);
729 file_free(vis, vis->error_file);
730 for (int i = 0; i < LENGTH(vis->registers); i++)
731 register_release(&vis->registers[i]);
732 vis->ui->free(vis->ui);
733 if (vis->usercmds) {
734 const char *name;
735 while (map_first(vis->usercmds, &name) && vis_cmd_unregister(vis, name));
737 map_free(vis->usercmds);
738 map_free(vis->cmds);
739 if (vis->options) {
740 const char *name;
741 while (map_first(vis->options, &name) && vis_option_unregister(vis, name));
743 map_free(vis->options);
744 map_free(vis->actions);
745 map_free(vis->keymap);
746 buffer_release(&vis->input_queue);
747 for (int i = 0; i < VIS_MODE_INVALID; i++)
748 map_free(vis_modes[i].bindings);
749 array_release_full(&vis->operators);
750 array_release_full(&vis->motions);
751 array_release_full(&vis->textobjects);
752 while (array_length(&vis->bindings))
753 vis_binding_free(vis, array_get_ptr(&vis->bindings, 0));
754 array_release(&vis->bindings);
755 while (array_length(&vis->actions_user))
756 vis_action_free(vis, array_get_ptr(&vis->actions_user, 0));
757 array_release(&vis->actions_user);
758 free(vis->shell);
759 free(vis);
762 void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) {
763 Win *win = vis->win;
764 if (!win)
765 return;
766 text_insert(win->file->text, pos, data, len);
767 vis_window_invalidate(win);
770 void vis_insert_key(Vis *vis, const char *data, size_t len) {
771 Win *win = vis->win;
772 if (!win)
773 return;
774 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s)) {
775 size_t pos = view_cursors_pos(s);
776 vis_insert(vis, pos, data, len);
777 view_cursors_scroll_to(s, pos + len);
781 void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) {
782 Win *win = vis->win;
783 if (!win)
784 return;
785 Text *txt = win->file->text;
786 Iterator it = text_iterator_get(txt, pos);
787 int chars = text_char_count(data, len);
788 for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\n'; )
789 text_iterator_char_next(&it, NULL);
791 text_delete(txt, pos, it.pos - pos);
792 vis_insert(vis, pos, data, len);
795 void vis_replace_key(Vis *vis, const char *data, size_t len) {
796 Win *win = vis->win;
797 if (!win)
798 return;
799 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s)) {
800 size_t pos = view_cursors_pos(s);
801 vis_replace(vis, pos, data, len);
802 view_cursors_scroll_to(s, pos + len);
806 void vis_delete(Vis *vis, size_t pos, size_t len) {
807 Win *win = vis->win;
808 if (!win)
809 return;
810 text_delete(win->file->text, pos, len);
811 vis_window_invalidate(win);
814 bool vis_action_register(Vis *vis, const KeyAction *action) {
815 return map_put(vis->actions, action->name, action);
818 bool vis_keymap_add(Vis *vis, const char *key, const char *mapping) {
819 return map_put(vis->keymap, key, mapping);
822 void vis_keymap_disable(Vis *vis) {
823 vis->keymap_disabled = true;
826 void vis_interrupt(Vis *vis) {
827 vis->interrupted = true;
830 bool vis_interrupt_requested(Vis *vis) {
831 return vis->interrupted;
834 void vis_do(Vis *vis) {
835 Win *win = vis->win;
836 if (!win)
837 return;
838 File *file = win->file;
839 Text *txt = file->text;
840 View *view = win->view;
841 Action *a = &vis->action;
843 int count = MAX(a->count, 1);
844 if (a->op == &vis_operators[VIS_OP_MODESWITCH])
845 count = 1; /* count should apply to inserted text not motion */
846 bool repeatable = a->op && !vis->macro_operator && !vis->win->parent;
847 bool multiple_cursors = view_selections_count(view) > 1;
849 bool linewise = !(a->type & CHARWISE) && (
850 a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) ||
851 vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]);
853 Register *reg = a->reg;
854 size_t reg_slot = multiple_cursors ? EPOS : 0;
855 size_t last_reg_slot = reg_slot;
856 if (!reg)
857 reg = &vis->registers[file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT];
858 if (a->op == &vis_operators[VIS_OP_PUT_AFTER] && multiple_cursors && vis_register_count(vis, reg) == 1)
859 reg_slot = 0;
861 if (vis->mode->visual && a->op)
862 window_selection_save(win);
864 for (Selection *sel = view_selections(view), *next; sel; sel = next) {
865 if (vis->interrupted)
866 break;
868 next = view_selections_next(sel);
870 size_t pos = view_cursors_pos(sel);
871 if (pos == EPOS) {
872 if (!view_selections_dispose(sel))
873 view_cursors_to(sel, 0);
874 continue;
877 OperatorContext c = {
878 .count = count,
879 .pos = pos,
880 .newpos = EPOS,
881 .range = text_range_empty(),
882 .reg = reg,
883 .reg_slot = reg_slot == EPOS ? (size_t)view_selections_number(sel) : reg_slot,
884 .linewise = linewise,
885 .arg = &a->arg,
886 .context = a->op ? a->op->context : NULL,
889 last_reg_slot = c.reg_slot;
891 bool err = false;
892 if (a->movement) {
893 size_t start = pos;
894 for (int i = 0; i < count; i++) {
895 size_t pos_prev = pos;
896 if (a->movement->txt)
897 pos = a->movement->txt(txt, pos);
898 else if (a->movement->cur)
899 pos = a->movement->cur(sel);
900 else if (a->movement->file)
901 pos = a->movement->file(vis, file, sel);
902 else if (a->movement->vis)
903 pos = a->movement->vis(vis, txt, pos);
904 else if (a->movement->view)
905 pos = a->movement->view(vis, view);
906 else if (a->movement->win)
907 pos = a->movement->win(vis, win, pos);
908 else if (a->movement->user)
909 pos = a->movement->user(vis, win, a->movement->data, pos);
910 if (pos == EPOS || a->movement->type & IDEMPOTENT || pos == pos_prev) {
911 err = a->movement->type & COUNT_EXACT;
912 break;
916 if (err) {
917 repeatable = false;
918 continue; // break?
921 if (pos == EPOS) {
922 c.range.start = start;
923 c.range.end = start;
924 pos = start;
925 } else {
926 c.range = text_range_new(start, pos);
927 c.newpos = pos;
930 if (!a->op) {
931 if (a->movement->type & CHARWISE)
932 view_cursors_scroll_to(sel, pos);
933 else
934 view_cursors_to(sel, pos);
935 if (vis->mode->visual)
936 c.range = view_selections_get(sel);
937 } else if (a->movement->type & INCLUSIVE && c.range.end > start) {
938 c.range.end = text_char_next(txt, c.range.end);
939 } else if (linewise && (a->movement->type & LINEWISE_INCLUSIVE)) {
940 c.range.end = text_char_next(txt, c.range.end);
942 } else if (a->textobj) {
943 if (vis->mode->visual)
944 c.range = view_selections_get(sel);
945 else
946 c.range.start = c.range.end = pos;
947 for (int i = 0; i < count; i++) {
948 Filerange r = text_range_empty();
949 if (a->textobj->txt)
950 r = a->textobj->txt(txt, pos);
951 else if (a->textobj->vis)
952 r = a->textobj->vis(vis, txt, pos);
953 else if (a->textobj->user)
954 r = a->textobj->user(vis, win, a->textobj->data, pos);
955 if (!text_range_valid(&r))
956 break;
957 if (a->textobj->type & TEXTOBJECT_DELIMITED_OUTER) {
958 r.start--;
959 r.end++;
962 if (vis->mode->visual || (i > 0 && !(a->textobj->type & TEXTOBJECT_NON_CONTIGUOUS)))
963 c.range = text_range_union(&c.range, &r);
964 else
965 c.range = r;
967 if (i < count - 1) {
968 if (a->textobj->type & TEXTOBJECT_EXTEND_BACKWARD) {
969 pos = c.range.start;
970 if ((a->textobj->type & TEXTOBJECT_DELIMITED_INNER) && pos > 0)
971 pos--;
972 } else {
973 pos = c.range.end;
974 if (a->textobj->type & TEXTOBJECT_DELIMITED_INNER)
975 pos++;
979 } else if (vis->mode->visual) {
980 c.range = view_selections_get(sel);
981 if (!text_range_valid(&c.range))
982 c.range.start = c.range.end = pos;
985 if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL])
986 c.range = text_range_linewise(txt, &c.range);
987 if (vis->mode->visual) {
988 view_selections_set(sel, &c.range);
989 view_selections_anchor(sel, true);
992 if (a->op) {
993 size_t pos = a->op->func(vis, txt, &c);
994 if (pos == EPOS) {
995 view_selections_dispose(sel);
996 } else if (pos <= text_size(txt)) {
997 view_selection_clear(sel);
998 view_cursors_to(sel, pos);
1003 view_selections_normalize(view);
1004 if (a->movement && (a->movement->type & JUMP))
1005 vis_jumplist_save(vis);
1007 if (a->op) {
1009 if (a->op == &vis_operators[VIS_OP_YANK] ||
1010 a->op == &vis_operators[VIS_OP_DELETE] ||
1011 a->op == &vis_operators[VIS_OP_CHANGE] ||
1012 a->op == &vis_operators[VIS_OP_REPLACE]) {
1013 register_resize(reg, last_reg_slot+1);
1016 /* we do not support visual repeat, still do something resonable */
1017 if (vis->mode->visual && !a->movement && !a->textobj)
1018 a->movement = &vis_motions[VIS_MOVE_NOP];
1020 /* operator implementations must not change the mode,
1021 * they might get called multiple times (once for every cursor)
1023 if (a->op == &vis_operators[VIS_OP_CHANGE]) {
1024 vis_mode_switch(vis, VIS_MODE_INSERT);
1025 } else if (a->op == &vis_operators[VIS_OP_MODESWITCH]) {
1026 vis_mode_switch(vis, a->mode);
1027 } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) {
1028 mode_set(vis, vis->mode_prev);
1029 } else if (vis->mode->visual) {
1030 vis_mode_switch(vis, VIS_MODE_NORMAL);
1033 if (vis->mode == &vis_modes[VIS_MODE_NORMAL])
1034 vis_file_snapshot(vis, file);
1035 vis_draw(vis);
1038 if (a != &vis->action_prev) {
1039 if (repeatable) {
1040 if (!a->macro)
1041 a->macro = vis->macro_operator;
1042 vis->action_prev = *a;
1044 action_reset(a);
1048 void action_reset(Action *a) {
1049 memset(a, 0, sizeof(*a));
1050 a->count = VIS_COUNT_UNKNOWN;
1053 void vis_cancel(Vis *vis) {
1054 action_reset(&vis->action);
1057 void vis_die(Vis *vis, const char *msg, ...) {
1058 va_list ap;
1059 va_start(ap, msg);
1060 vis->ui->die(vis->ui, msg, ap);
1061 va_end(ap);
1064 const char *vis_keys_next(Vis *vis, const char *keys) {
1065 if (!keys || !*keys)
1066 return NULL;
1067 TermKeyKey key;
1068 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1069 const char *next = NULL;
1070 /* first try to parse a special key of the form <Key> */
1071 if (*keys == '<' && keys[1] && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1072 return next+1;
1073 if (strncmp(keys, "<vis-", 5) == 0) {
1074 const char *start = keys + 1, *end = start;
1075 while (*end && *end != '>')
1076 end++;
1077 if (end > start && end - start - 1 < VIS_KEY_LENGTH_MAX && *end == '>') {
1078 char key[VIS_KEY_LENGTH_MAX];
1079 memcpy(key, start, end - start);
1080 key[end - start] = '\0';
1081 if (map_get(vis->actions, key))
1082 return end + 1;
1085 if (ISUTF8(*keys))
1086 keys++;
1087 while (!ISUTF8(*keys))
1088 keys++;
1089 return keys;
1092 long vis_keys_codepoint(Vis *vis, const char *keys) {
1093 long codepoint = -1;
1094 const char *next;
1095 TermKeyKey key;
1096 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1098 if (!keys[0])
1099 return -1;
1100 if (keys[0] == '<' && !keys[1])
1101 return '<';
1103 if (keys[0] == '<' && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1104 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1105 else if ((next = termkey_strpkey(termkey, keys, &key, TERMKEY_FORMAT_VIM)))
1106 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1108 if (codepoint != -1) {
1109 if (key.modifiers == TERMKEY_KEYMOD_CTRL)
1110 codepoint &= 0x1f;
1111 return codepoint;
1114 if (!next || key.type != TERMKEY_TYPE_KEYSYM)
1115 return -1;
1117 const int keysym[] = {
1118 TERMKEY_SYM_ENTER, '\n',
1119 TERMKEY_SYM_TAB, '\t',
1120 TERMKEY_SYM_BACKSPACE, '\b',
1121 TERMKEY_SYM_ESCAPE, 0x1b,
1122 TERMKEY_SYM_DELETE, 0x7f,
1126 for (const int *k = keysym; k[0]; k += 2) {
1127 if (key.code.sym == k[0])
1128 return k[1];
1131 return -1;
1134 bool vis_keys_utf8(Vis *vis, const char *keys, char utf8[static UTFmax+1]) {
1135 Rune rune = vis_keys_codepoint(vis, keys);
1136 if (rune == (Rune)-1)
1137 return false;
1138 size_t len = runetochar(utf8, &rune);
1139 utf8[len] = '\0';
1140 return true;
1143 typedef struct {
1144 Vis *vis;
1145 size_t len; // length of the prefix
1146 int count; // how many bindings can complete this prefix
1147 bool angle_bracket; // does the prefix end with '<'
1148 } PrefixCompletion;
1150 static bool isprefix(const char *key, void *value, void *data) {
1151 PrefixCompletion *completion = data;
1152 if (!completion->angle_bracket) {
1153 completion->count++;
1154 } else {
1155 const char *start = key + completion->len;
1156 const char *end = vis_keys_next(completion->vis, start);
1157 if (end && start + 1 == end)
1158 completion->count++;
1160 return completion->count == 1;
1163 static void vis_keys_process(Vis *vis, size_t pos) {
1164 Buffer *buf = &vis->input_queue;
1165 char *keys = buf->data + pos, *start = keys, *cur = keys, *end = keys, *binding_end = keys;;
1166 bool prefix = false;
1167 KeyBinding *binding = NULL;
1169 while (cur && *cur) {
1171 if (!(end = (char*)vis_keys_next(vis, cur))) {
1172 buffer_remove(buf, keys - buf->data, strlen(keys));
1173 return;
1176 char tmp = *end;
1177 *end = '\0';
1178 prefix = false;
1180 for (Mode *global_mode = vis->mode; global_mode && !prefix; global_mode = global_mode->parent) {
1181 for (int global = 0; global < 2 && !prefix; global++) {
1182 Mode *mode = (global || !vis->win) ?
1183 global_mode :
1184 &vis->win->modes[global_mode->id];
1185 if (!mode->bindings)
1186 continue;
1187 /* keep track of longest matching binding */
1188 KeyBinding *match = map_get(mode->bindings, start);
1189 if (match && end > binding_end) {
1190 binding = match;
1191 binding_end = end;
1194 const Map *pmap = map_prefix(mode->bindings, start);
1195 PrefixCompletion completions = {
1196 .vis = vis,
1197 .len = cur - start,
1198 .count = 0,
1199 .angle_bracket = !strcmp(cur, "<"),
1201 map_iterate(pmap, isprefix, &completions);
1203 prefix = (!match && completions.count > 0) ||
1204 ( match && completions.count > 1);
1208 *end = tmp;
1210 if (prefix) {
1211 /* input sofar is ambigious, wait for more */
1212 cur = end;
1213 end = start;
1214 } else if (binding) { /* exact match */
1215 if (binding->action) {
1216 size_t len = binding_end - start;
1217 strcpy(vis->key_prev, vis->key_current);
1218 strncpy(vis->key_current, start, len);
1219 vis->key_current[len] = '\0';
1220 end = (char*)binding->action->func(vis, binding_end, &binding->action->arg);
1221 if (!end) {
1222 end = start;
1223 break;
1225 start = cur = end;
1226 } else if (binding->alias) {
1227 buffer_remove(buf, start - buf->data, binding_end - start);
1228 buffer_insert0(buf, start - buf->data, binding->alias);
1229 cur = end = start;
1231 binding = NULL;
1232 binding_end = start;
1233 } else { /* no keybinding */
1234 KeyAction *action = NULL;
1235 if (start[0] == '<' && end[-1] == '>') {
1236 /* test for special editor key command */
1237 char tmp = end[-1];
1238 end[-1] = '\0';
1239 action = map_get(vis->actions, start+1);
1240 end[-1] = tmp;
1241 if (action) {
1242 size_t len = end - start;
1243 strcpy(vis->key_prev, vis->key_current);
1244 strncpy(vis->key_current, start, len);
1245 vis->key_current[len] = '\0';
1246 end = (char*)action->func(vis, end, &action->arg);
1247 if (!end) {
1248 end = start;
1249 break;
1253 if (!action && vis->mode->input) {
1254 end = (char*)vis_keys_next(vis, start);
1255 vis->mode->input(vis, start, end - start);
1257 start = cur = end;
1261 buffer_remove(buf, keys - buf->data, end - keys);
1264 void vis_keys_feed(Vis *vis, const char *input) {
1265 if (!input)
1266 return;
1267 Macro macro;
1268 macro_init(&macro);
1269 if (!macro_append(&macro, input))
1270 return;
1271 /* use internal function, to keep Lua based tests which use undo points working */
1272 macro_replay_internal(vis, &macro);
1273 macro_release(&macro);
1276 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record) {
1277 if (!input)
1278 return;
1279 if (record && vis->recording)
1280 macro_append(vis->recording, input);
1281 if (vis->macro_operator)
1282 macro_append(vis->macro_operator, input);
1283 if (buffer_append0(&vis->input_queue, input))
1284 vis_keys_process(vis, pos);
1287 static const char *getkey(Vis *vis) {
1288 TermKeyKey key = { 0 };
1289 if (!vis->ui->getkey(vis->ui, &key))
1290 return NULL;
1291 vis_info_hide(vis);
1292 bool use_keymap = vis->mode->id != VIS_MODE_INSERT &&
1293 vis->mode->id != VIS_MODE_REPLACE &&
1294 !vis->keymap_disabled;
1295 vis->keymap_disabled = false;
1296 if (key.type == TERMKEY_TYPE_UNICODE && use_keymap) {
1297 const char *mapped = map_get(vis->keymap, key.utf8);
1298 if (mapped) {
1299 size_t len = strlen(mapped)+1;
1300 if (len <= sizeof(key.utf8))
1301 memcpy(key.utf8, mapped, len);
1305 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1306 termkey_strfkey(termkey, vis->key, sizeof(vis->key), &key, TERMKEY_FORMAT_VIM);
1307 return vis->key;
1310 bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const void *context) {
1311 switch (signum) {
1312 case SIGBUS:
1313 for (File *file = vis->files; file; file = file->next) {
1314 if (text_mmaped(file->text, siginfo->si_addr))
1315 file->truncated = true;
1317 vis->sigbus = true;
1318 if (vis->running)
1319 siglongjmp(vis->sigbus_jmpbuf, 1);
1320 return true;
1321 case SIGINT:
1322 vis->interrupted = true;
1323 return true;
1324 case SIGCONT:
1325 vis->resume = true;
1326 /* fall through */
1327 case SIGWINCH:
1328 vis->need_resize = true;
1329 return true;
1330 case SIGTERM:
1331 case SIGHUP:
1332 vis->terminate = true;
1333 return true;
1335 return false;
1338 int vis_run(Vis *vis) {
1339 if (!vis->windows)
1340 return EXIT_SUCCESS;
1341 if (vis->exit_status != -1)
1342 return vis->exit_status;
1343 vis->running = true;
1345 vis_event_emit(vis, VIS_EVENT_START);
1347 struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL;
1349 sigset_t emptyset;
1350 sigemptyset(&emptyset);
1351 vis_draw(vis);
1352 vis->exit_status = EXIT_SUCCESS;
1354 sigsetjmp(vis->sigbus_jmpbuf, 1);
1356 while (vis->running) {
1357 fd_set fds;
1358 FD_ZERO(&fds);
1359 FD_SET(STDIN_FILENO, &fds);
1361 if (vis->sigbus) {
1362 char *name = NULL;
1363 for (Win *next, *win = vis->windows; win; win = next) {
1364 next = win->next;
1365 if (win->file->truncated) {
1366 free(name);
1367 name = strdup(win->file->name);
1368 vis_window_close(win);
1371 if (!vis->windows)
1372 vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1373 else
1374 vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1375 vis->sigbus = false;
1376 free(name);
1379 if (vis->terminate)
1380 vis_die(vis, "Killed by SIGTERM\n");
1381 if (vis->interrupted) {
1382 vis->interrupted = false;
1383 vis_keys_feed(vis, "<C-c>");
1386 if (vis->resume) {
1387 vis_resume(vis);
1388 vis->resume = false;
1391 if (vis->need_resize) {
1392 vis->ui->resize(vis->ui);
1393 vis->need_resize = false;
1396 vis_update(vis);
1397 idle.tv_sec = vis->mode->idle_timeout;
1398 int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
1399 if (r == -1 && errno == EINTR)
1400 continue;
1402 if (r < 0) {
1403 /* TODO save all pending changes to a ~suffixed file */
1404 vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
1407 if (!FD_ISSET(STDIN_FILENO, &fds)) {
1408 if (vis->mode->idle)
1409 vis->mode->idle(vis);
1410 timeout = NULL;
1411 continue;
1414 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1415 termkey_advisereadable(termkey);
1416 const char *key;
1418 while ((key = getkey(vis)))
1419 vis_keys_push(vis, key, 0, true);
1421 if (vis->mode->idle)
1422 timeout = &idle;
1424 return vis->exit_status;
1427 Macro *macro_get(Vis *vis, enum VisRegister id) {
1428 if (id == VIS_MACRO_LAST_RECORDED)
1429 return vis->last_recording;
1430 if (VIS_REG_A <= id && id <= VIS_REG_Z)
1431 id -= VIS_REG_A;
1432 if (id < LENGTH(vis->registers))
1433 return array_get(&vis->registers[id].values, 0);
1434 return NULL;
1437 void macro_operator_record(Vis *vis) {
1438 if (vis->macro_operator)
1439 return;
1440 vis->macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
1441 macro_reset(vis->macro_operator);
1444 void macro_operator_stop(Vis *vis) {
1445 if (!vis->macro_operator)
1446 return;
1447 Macro *dot = macro_get(vis, VIS_REG_DOT);
1448 buffer_put(dot, vis->macro_operator->data, vis->macro_operator->len);
1449 vis->action_prev.macro = dot;
1450 vis->macro_operator = NULL;
1453 bool vis_macro_record(Vis *vis, enum VisRegister id) {
1454 Macro *macro = macro_get(vis, id);
1455 if (vis->recording || !macro)
1456 return false;
1457 if (!(VIS_REG_A <= id && id <= VIS_REG_Z))
1458 macro_reset(macro);
1459 vis->recording = macro;
1460 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1461 return true;
1464 bool vis_macro_record_stop(Vis *vis) {
1465 if (!vis->recording)
1466 return false;
1467 /* XXX: hack to remove last recorded key, otherwise upon replay
1468 * we would start another recording */
1469 if (vis->recording->len > 1) {
1470 vis->recording->len--;
1471 vis->recording->data[vis->recording->len-1] = '\0';
1473 vis->last_recording = vis->recording;
1474 vis->recording = NULL;
1475 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1476 return true;
1479 bool vis_macro_recording(Vis *vis) {
1480 return vis->recording;
1483 static void macro_replay(Vis *vis, const Macro *macro) {
1484 const Macro *replaying = vis->replaying;
1485 vis->replaying = macro;
1486 macro_replay_internal(vis, macro);
1487 vis->replaying = replaying;
1490 static void macro_replay_internal(Vis *vis, const Macro *macro) {
1491 size_t pos = buffer_length0(&vis->input_queue);
1492 for (char *key = macro->data, *next; key; key = next) {
1493 char tmp;
1494 next = (char*)vis_keys_next(vis, key);
1495 if (next) {
1496 tmp = *next;
1497 *next = '\0';
1500 vis_keys_push(vis, key, pos, false);
1502 if (next)
1503 *next = tmp;
1507 bool vis_macro_replay(Vis *vis, enum VisRegister id) {
1508 if (id == VIS_REG_SEARCH)
1509 return vis_motion(vis, VIS_MOVE_SEARCH_REPEAT_FORWARD);
1510 if (id == VIS_REG_COMMAND) {
1511 const char *cmd = register_get(vis, &vis->registers[id], NULL);
1512 return vis_cmd(vis, cmd);
1515 Macro *macro = macro_get(vis, id);
1516 if (!macro || macro == vis->recording)
1517 return false;
1518 int count = vis_count_get_default(vis, 1);
1519 vis_cancel(vis);
1520 for (int i = 0; i < count; i++)
1521 macro_replay(vis, macro);
1522 Win *win = vis->win;
1523 if (win)
1524 vis_file_snapshot(vis, win->file);
1525 return true;
1528 void vis_repeat(Vis *vis) {
1529 const Macro *macro = vis->action_prev.macro;
1530 int count = vis->action.count;
1531 if (count != VIS_COUNT_UNKNOWN)
1532 vis->action_prev.count = count;
1533 else
1534 count = vis->action_prev.count;
1535 vis->action = vis->action_prev;
1536 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
1537 vis_do(vis);
1538 if (macro) {
1539 Mode *mode = vis->mode;
1540 Action action_prev = vis->action_prev;
1541 if (count < 1 || action_prev.op == &vis_operators[VIS_OP_CHANGE])
1542 count = 1;
1543 if (vis->action_prev.op == &vis_operators[VIS_OP_MODESWITCH])
1544 vis->action_prev.count = 1;
1545 for (int i = 0; i < count; i++) {
1546 if (vis->interrupted)
1547 break;
1548 mode_set(vis, mode);
1549 macro_replay(vis, macro);
1551 vis->action_prev = action_prev;
1553 vis_cancel(vis);
1554 Win *win = vis->win;
1555 if (win)
1556 vis_file_snapshot(vis, win->file);
1559 int vis_count_get(Vis *vis) {
1560 return vis->action.count;
1563 int vis_count_get_default(Vis *vis, int def) {
1564 if (vis->action.count == VIS_COUNT_UNKNOWN)
1565 return def;
1566 return vis->action.count;
1569 void vis_count_set(Vis *vis, int count) {
1570 vis->action.count = (count >= 0 ? count : VIS_COUNT_UNKNOWN);
1573 VisCountIterator vis_count_iterator_get(Vis *vis, int def) {
1574 return (VisCountIterator) {
1575 .vis = vis,
1576 .iteration = 0,
1577 .count = vis_count_get_default(vis, def),
1581 VisCountIterator vis_count_iterator_init(Vis *vis, int count) {
1582 return (VisCountIterator) {
1583 .vis = vis,
1584 .iteration = 0,
1585 .count = count,
1589 bool vis_count_iterator_next(VisCountIterator *it) {
1590 if (it->vis->interrupted)
1591 return false;
1592 return it->iteration++ < it->count;
1595 void vis_exit(Vis *vis, int status) {
1596 vis->running = false;
1597 vis->exit_status = status;
1600 void vis_insert_tab(Vis *vis) {
1601 Win *win = vis->win;
1602 if (!win)
1603 return;
1604 if (!vis->expandtab) {
1605 vis_insert_key(vis, "\t", 1);
1606 return;
1608 char spaces[9];
1609 int tabwidth = MIN(vis->tabwidth, LENGTH(spaces) - 1);
1610 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s)) {
1611 size_t pos = view_cursors_pos(s);
1612 int width = text_line_width_get(win->file->text, pos);
1613 int count = tabwidth - (width % tabwidth);
1614 for (int i = 0; i < count; i++)
1615 spaces[i] = ' ';
1616 spaces[count] = '\0';
1617 vis_insert(vis, pos, spaces, count);
1618 view_cursors_scroll_to(s, pos + count);
1622 size_t vis_text_insert_nl(Vis *vis, Text *txt, size_t pos) {
1623 size_t indent_len = 0;
1624 char byte, *indent = NULL;
1625 /* insert second newline at end of file, except if there is already one */
1626 bool eof = pos == text_size(txt);
1627 bool nl2 = eof && !(pos > 0 && text_byte_get(txt, pos-1, &byte) && byte == '\n');
1629 if (vis->autoindent) {
1630 /* copy leading white space of current line */
1631 size_t begin = text_line_begin(txt, pos);
1632 size_t start = text_line_start(txt, begin);
1633 size_t end = text_line_end(txt, start);
1634 if (start > pos)
1635 start = pos;
1636 indent_len = start >= begin ? start-begin : 0;
1637 if (start == end) {
1638 pos = begin;
1639 } else {
1640 indent = malloc(indent_len+1);
1641 if (indent)
1642 indent_len = text_bytes_get(txt, begin, indent_len, indent);
1646 text_insert(txt, pos, "\n", 1);
1647 if (eof) {
1648 if (nl2)
1649 text_insert(txt, text_size(txt), "\n", 1);
1650 else
1651 pos--; /* place cursor before, not after nl */
1653 pos++;
1655 if (indent)
1656 text_insert(txt, pos, indent, indent_len);
1657 free(indent);
1658 return pos + indent_len;
1661 void vis_insert_nl(Vis *vis) {
1662 Win *win = vis->win;
1663 if (!win)
1664 return;
1665 View *view = win->view;
1666 Text *txt = win->file->text;
1667 for (Selection *s = view_selections(view); s; s = view_selections_next(s)) {
1668 size_t pos = view_cursors_pos(s);
1669 size_t newpos = vis_text_insert_nl(vis, txt, pos);
1670 /* This is a bit of a hack to fix cursor positioning when
1671 * inserting a new line at the start of the view port.
1672 * It has the effect of reseting the mark used by the view
1673 * code to keep track of the start of the visible region.
1675 view_cursors_to(s, pos);
1676 view_cursors_to(s, newpos);
1678 vis_window_invalidate(win);
1681 Regex *vis_regex(Vis *vis, const char *pattern) {
1682 if (!pattern && !(pattern = register_get(vis, &vis->registers[VIS_REG_SEARCH], NULL)))
1683 return NULL;
1684 Regex *regex = text_regex_new();
1685 if (!regex)
1686 return NULL;
1687 if (text_regex_compile(regex, pattern, REG_EXTENDED|REG_NEWLINE) != 0) {
1688 text_regex_free(regex);
1689 return NULL;
1691 register_put0(vis, &vis->registers[VIS_REG_SEARCH], pattern);
1692 return regex;
1695 int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
1696 void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
1697 void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len)) {
1699 /* if an invalid range was given, stdin (i.e. key board input) is passed
1700 * through the external command. */
1701 Text *text = file->text;
1702 int pin[2], pout[2], perr[2], status = -1;
1703 bool interactive = !text_range_valid(range);
1704 Filerange rout = interactive ? text_range_new(0, 0) : *range;
1706 if (pipe(pin) == -1)
1707 return -1;
1708 if (pipe(pout) == -1) {
1709 close(pin[0]);
1710 close(pin[1]);
1711 return -1;
1714 if (pipe(perr) == -1) {
1715 close(pin[0]);
1716 close(pin[1]);
1717 close(pout[0]);
1718 close(pout[1]);
1719 return -1;
1722 vis->ui->terminal_save(vis->ui);
1723 pid_t pid = fork();
1725 if (pid == -1) {
1726 close(pin[0]);
1727 close(pin[1]);
1728 close(pout[0]);
1729 close(pout[1]);
1730 close(perr[0]);
1731 close(perr[1]);
1732 vis_info_show(vis, "fork failure: %s", strerror(errno));
1733 return -1;
1734 } else if (pid == 0) { /* child i.e filter */
1735 sigset_t sigterm_mask;
1736 sigemptyset(&sigterm_mask);
1737 sigaddset(&sigterm_mask, SIGTERM);
1738 if (sigprocmask(SIG_UNBLOCK, &sigterm_mask, NULL) == -1) {
1739 fprintf(stderr, "failed to reset signal mask");
1740 exit(EXIT_FAILURE);
1743 int null = open("/dev/null", O_RDWR);
1744 if (null == -1) {
1745 fprintf(stderr, "failed to open /dev/null");
1746 exit(EXIT_FAILURE);
1749 if (!interactive) {
1750 /* If we have nothing to write, let stdin point to
1751 * /dev/null instead of a pipe which is immediately
1752 * closed. Some programs behave differently when used
1753 * in a pipeline.
1755 if (text_range_size(range) == 0)
1756 dup2(null, STDIN_FILENO);
1757 else
1758 dup2(pin[0], STDIN_FILENO);
1761 close(pin[0]);
1762 close(pin[1]);
1763 if (interactive) {
1764 dup2(STDERR_FILENO, STDOUT_FILENO);
1765 /* For some reason the first byte written by the
1766 * interactive application is not being displayed.
1767 * It probably has something to do with the terminal
1768 * state change. By writing a dummy byte ourself we
1769 * ensure that the complete output is visible.
1771 while(write(STDOUT_FILENO, " ", 1) == -1 && errno == EINTR);
1772 } else if (read_stdout) {
1773 dup2(pout[1], STDOUT_FILENO);
1774 } else {
1775 dup2(null, STDOUT_FILENO);
1777 close(pout[1]);
1778 close(pout[0]);
1779 if (!interactive) {
1780 if (read_stderr)
1781 dup2(perr[1], STDERR_FILENO);
1782 else
1783 dup2(null, STDERR_FILENO);
1785 close(perr[0]);
1786 close(perr[1]);
1787 close(null);
1789 if (file->name) {
1790 char *name = strrchr(file->name, '/');
1791 setenv("vis_filepath", file->name, 1);
1792 setenv("vis_filename", name ? name+1 : file->name, 1);
1795 if (!argv[1])
1796 execlp(vis->shell, vis->shell, "-c", argv[0], (char*)NULL);
1797 else
1798 execvp(argv[0], (char* const*)argv);
1799 fprintf(stderr, "exec failure: %s", strerror(errno));
1800 exit(EXIT_FAILURE);
1803 vis->interrupted = false;
1805 close(pin[0]);
1806 close(pout[1]);
1807 close(perr[1]);
1809 if (fcntl(pout[0], F_SETFL, O_NONBLOCK) == -1 ||
1810 fcntl(perr[0], F_SETFL, O_NONBLOCK) == -1)
1811 goto err;
1813 fd_set rfds, wfds;
1815 do {
1816 if (vis->interrupted) {
1817 kill(0, SIGTERM);
1818 break;
1821 FD_ZERO(&rfds);
1822 FD_ZERO(&wfds);
1823 if (pin[1] != -1)
1824 FD_SET(pin[1], &wfds);
1825 if (pout[0] != -1)
1826 FD_SET(pout[0], &rfds);
1827 if (perr[0] != -1)
1828 FD_SET(perr[0], &rfds);
1830 if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) {
1831 if (errno == EINTR)
1832 continue;
1833 vis_info_show(vis, "Select failure");
1834 break;
1837 if (pin[1] != -1 && FD_ISSET(pin[1], &wfds)) {
1838 Filerange junk = rout;
1839 if (junk.end > junk.start + PIPE_BUF)
1840 junk.end = junk.start + PIPE_BUF;
1841 ssize_t len = text_write_range(text, &junk, pin[1]);
1842 if (len > 0) {
1843 rout.start += len;
1844 if (text_range_size(&rout) == 0) {
1845 close(pout[1]);
1846 pout[1] = -1;
1848 } else {
1849 close(pin[1]);
1850 pin[1] = -1;
1851 if (len == -1)
1852 vis_info_show(vis, "Error writing to external command");
1856 if (pout[0] != -1 && FD_ISSET(pout[0], &rfds)) {
1857 char buf[BUFSIZ];
1858 ssize_t len = read(pout[0], buf, sizeof buf);
1859 if (len > 0) {
1860 if (read_stdout)
1861 (*read_stdout)(stdout_context, buf, len);
1862 } else if (len == 0) {
1863 close(pout[0]);
1864 pout[0] = -1;
1865 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1866 vis_info_show(vis, "Error reading from filter stdout");
1867 close(pout[0]);
1868 pout[0] = -1;
1872 if (perr[0] != -1 && FD_ISSET(perr[0], &rfds)) {
1873 char buf[BUFSIZ];
1874 ssize_t len = read(perr[0], buf, sizeof buf);
1875 if (len > 0) {
1876 if (read_stderr)
1877 (*read_stderr)(stderr_context, buf, len);
1878 } else if (len == 0) {
1879 close(perr[0]);
1880 perr[0] = -1;
1881 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1882 vis_info_show(vis, "Error reading from filter stderr");
1883 close(perr[0]);
1884 perr[0] = -1;
1888 } while (pin[1] != -1 || pout[0] != -1 || perr[0] != -1);
1890 err:
1891 if (pin[1] != -1)
1892 close(pin[1]);
1893 if (pout[0] != -1)
1894 close(pout[0]);
1895 if (perr[0] != -1)
1896 close(perr[0]);
1898 for (;;) {
1899 if (vis->interrupted)
1900 kill(0, SIGTERM);
1901 pid_t died = waitpid(pid, &status, 0);
1902 if ((died == -1 && errno == ECHILD) || pid == died)
1903 break;
1906 /* clear any pending SIGTERM */
1907 struct sigaction sigterm_ignore, sigterm_old;
1908 sigterm_ignore.sa_handler = SIG_IGN;
1909 sigterm_ignore.sa_flags = 0;
1910 sigemptyset(&sigterm_ignore.sa_mask);
1912 sigaction(SIGTERM, &sigterm_ignore, &sigterm_old);
1913 sigaction(SIGTERM, &sigterm_old, NULL);
1915 vis->interrupted = false;
1916 vis->ui->terminal_restore(vis->ui);
1918 return status;
1921 static ssize_t read_buffer(void *context, char *data, size_t len) {
1922 buffer_append(context, data, len);
1923 return len;
1926 int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[], char **out, char **err) {
1927 Buffer bufout, buferr;
1928 buffer_init(&bufout);
1929 buffer_init(&buferr);
1930 int status = vis_pipe(vis, file, range, argv,
1931 &bufout, out ? read_buffer : NULL,
1932 &buferr, err ? read_buffer : NULL);
1933 buffer_terminate(&bufout);
1934 buffer_terminate(&buferr);
1935 if (out)
1936 *out = buffer_move(&bufout);
1937 if (err)
1938 *err = buffer_move(&buferr);
1939 buffer_release(&bufout);
1940 buffer_release(&buferr);
1941 return status;
1944 bool vis_cmd(Vis *vis, const char *cmdline) {
1945 if (!cmdline)
1946 return true;
1947 while (*cmdline == ':')
1948 cmdline++;
1949 size_t len = strlen(cmdline);
1950 char *line = malloc(len+2);
1951 if (!line)
1952 return false;
1953 strncpy(line, cmdline, len+1);
1955 for (char *end = line + len - 1; end >= line && isspace((unsigned char)*end); end--)
1956 *end = '\0';
1958 enum SamError err = sam_cmd(vis, line);
1959 if (err != SAM_ERR_OK)
1960 vis_info_show(vis, "%s", sam_error(err));
1961 free(line);
1962 return err == SAM_ERR_OK;
1965 void vis_file_snapshot(Vis *vis, File *file) {
1966 if (!vis->replaying)
1967 text_snapshot(file->text);
1970 Text *vis_text(Vis *vis) {
1971 Win *win = vis->win;
1972 return win ? win->file->text : NULL;
1975 View *vis_view(Vis *vis) {
1976 Win *win = vis->win;
1977 return win ? win->view : NULL;
1980 Win *vis_window(Vis *vis) {
1981 return vis->win;
1984 bool vis_get_autoindent(const Vis *vis) {
1985 return vis->autoindent;