Merge branch 'paranthese-typo' of https://github.com/Two-Finger/vis
[vis.git] / vis.c
blob431c7790c6746f8af2556a4b2975753919595744
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 static void macro_replay(Vis *vis, const Macro *macro);
32 static void macro_replay_internal(Vis *vis, const Macro *macro);
33 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record);
35 bool vis_event_emit(Vis *vis, enum VisEvents id, ...) {
36 if (!vis->event)
37 return true;
39 if (!vis->initialized) {
40 vis->initialized = true;
41 vis->ui->init(vis->ui, vis);
42 if (vis->event->init)
43 vis->event->init(vis);
46 va_list ap;
47 va_start(ap, id);
48 bool ret = true;
50 switch (id) {
51 case VIS_EVENT_INIT:
52 break;
53 case VIS_EVENT_START:
54 if (vis->event->start)
55 vis->event->start(vis);
56 break;
57 case VIS_EVENT_FILE_OPEN:
58 case VIS_EVENT_FILE_SAVE_PRE:
59 case VIS_EVENT_FILE_SAVE_POST:
60 case VIS_EVENT_FILE_CLOSE:
62 File *file = va_arg(ap, File*);
63 if (file->internal)
64 break;
65 if (id == VIS_EVENT_FILE_OPEN && vis->event->file_open) {
66 vis->event->file_open(vis, file);
67 } else if (id == VIS_EVENT_FILE_SAVE_PRE && vis->event->file_save_pre) {
68 const char *path = va_arg(ap, const char*);
69 ret = vis->event->file_save_pre(vis, file, path);
70 } else if (id == VIS_EVENT_FILE_SAVE_POST && vis->event->file_save_post) {
71 const char *path = va_arg(ap, const char*);
72 vis->event->file_save_post(vis, file, path);
73 } else if (id == VIS_EVENT_FILE_CLOSE && vis->event->file_close) {
74 vis->event->file_close(vis, file);
76 break;
78 case VIS_EVENT_WIN_OPEN:
79 case VIS_EVENT_WIN_CLOSE:
80 case VIS_EVENT_WIN_HIGHLIGHT:
81 case VIS_EVENT_WIN_STATUS:
83 Win *win = va_arg(ap, Win*);
84 if (win->file->internal && id != VIS_EVENT_WIN_STATUS)
85 break;
86 if (vis->event->win_open && id == VIS_EVENT_WIN_OPEN) {
87 vis->event->win_open(vis, win);
88 } else if (vis->event->win_close && id == VIS_EVENT_WIN_CLOSE) {
89 vis->event->win_close(vis, win);
90 } else if (vis->event->win_highlight && id == VIS_EVENT_WIN_HIGHLIGHT) {
91 vis->event->win_highlight(vis, win);
92 } else if (vis->event->win_status && id == VIS_EVENT_WIN_STATUS) {
93 vis->event->win_status(vis, win);
95 break;
97 case VIS_EVENT_QUIT:
98 if (vis->event->quit)
99 vis->event->quit(vis);
100 break;
103 va_end(ap);
104 return ret;
107 /** window / file handling */
109 static void file_free(Vis *vis, File *file) {
110 if (!file)
111 return;
112 if (file->refcount > 1) {
113 --file->refcount;
114 return;
116 vis_event_emit(vis, VIS_EVENT_FILE_CLOSE, file);
117 for (size_t i = 0; i < LENGTH(file->marks); i++)
118 mark_release(&file->marks[i]);
119 text_free(file->text);
120 free((char*)file->name);
122 if (file->prev)
123 file->prev->next = file->next;
124 if (file->next)
125 file->next->prev = file->prev;
126 if (vis->files == file)
127 vis->files = file->next;
128 free(file);
131 static File *file_new_text(Vis *vis, Text *text) {
132 File *file = calloc(1, sizeof(*file));
133 if (!file)
134 return NULL;
135 file->fd = -1;
136 file->text = text;
137 file->stat = text_stat(text);
138 for (size_t i = 0; i < LENGTH(file->marks); i++)
139 mark_init(&file->marks[i]);
140 if (vis->files)
141 vis->files->prev = file;
142 file->next = vis->files;
143 vis->files = file;
144 return file;
147 static char *absolute_path(const char *name) {
148 if (!name)
149 return NULL;
150 char *copy1 = strdup(name);
151 char *copy2 = strdup(name);
152 char *path_absolute = NULL;
153 char path_normalized[PATH_MAX] = "";
155 if (!copy1 || !copy2)
156 goto err;
158 char *dir = dirname(copy1);
159 char *base = basename(copy2);
160 if (!(path_absolute = realpath(dir, NULL)))
161 goto err;
162 if (strcmp(path_absolute, "/") == 0)
163 path_absolute[0] = '\0';
165 snprintf(path_normalized, sizeof(path_normalized), "%s/%s",
166 path_absolute, base);
167 err:
168 free(copy1);
169 free(copy2);
170 free(path_absolute);
171 return path_normalized[0] ? strdup(path_normalized) : NULL;
174 static File *file_new(Vis *vis, const char *name) {
175 char *name_absolute = NULL;
176 if (name) {
177 if (!(name_absolute = absolute_path(name)))
178 return NULL;
179 File *existing = NULL;
180 /* try to detect whether the same file is already open in another window
181 * TODO: do this based on inodes */
182 for (File *file = vis->files; file; file = file->next) {
183 if (file->name && strcmp(file->name, name_absolute) == 0) {
184 existing = file;
185 break;
188 if (existing) {
189 free(name_absolute);
190 return existing;
194 File *file = NULL;
195 Text *text = text_load(name);
196 if (!text && name && errno == ENOENT)
197 text = text_load(NULL);
198 if (!text)
199 goto err;
200 if (!(file = file_new_text(vis, text)))
201 goto err;
202 file->name = name_absolute;
203 vis_event_emit(vis, VIS_EVENT_FILE_OPEN, file);
204 return file;
205 err:
206 free(name_absolute);
207 text_free(text);
208 file_free(vis, file);
209 return NULL;
212 static File *file_new_internal(Vis *vis, const char *filename) {
213 File *file = file_new(vis, filename);
214 if (file) {
215 file->refcount = 1;
216 file->internal = true;
218 return file;
221 void file_name_set(File *file, const char *name) {
222 if (name == file->name)
223 return;
224 free((char*)file->name);
225 file->name = absolute_path(name);
228 const char *file_name_get(File *file) {
229 /* TODO: calculate path relative to working directory, cache result */
230 if (!file->name)
231 return NULL;
232 char cwd[PATH_MAX];
233 if (!getcwd(cwd, sizeof cwd))
234 return file->name;
235 const char *path = strstr(file->name, cwd);
236 if (path != file->name)
237 return file->name;
238 size_t cwdlen = strlen(cwd);
239 return file->name[cwdlen] == '/' ? file->name+cwdlen+1 : file->name;
242 void vis_window_status(Win *win, const char *status) {
243 win->ui->status(win->ui, status);
246 void window_selection_save(Win *win) {
247 Vis *vis = win->vis;
248 View *view = win->view;
249 Array sel = view_selections_get_all(view);
250 vis_mark_set(win, VIS_MARK_SELECTION, &sel);
251 array_release(&sel);
252 vis_jumplist_save(vis);
256 static void window_free(Win *win) {
257 if (!win)
258 return;
259 Vis *vis = win->vis;
260 for (Win *other = vis->windows; other; other = other->next) {
261 if (other->parent == win)
262 other->parent = NULL;
264 if (vis->ui)
265 vis->ui->window_free(win->ui);
266 view_free(win->view);
267 for (size_t i = 0; i < LENGTH(win->modes); i++)
268 map_free(win->modes[i].bindings);
269 marklist_release(&win->jumplist);
270 mark_release(&win->saved_selections);
271 free(win);
274 static void window_draw_colorcolumn(Win *win) {
275 View *view = win->view;
276 int cc = view_colorcolumn_get(view);
277 if (cc <= 0)
278 return;
279 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_COLOR_COLUMN);
280 size_t lineno = 0;
281 int line_cols = 0; /* Track the number of columns we've passed on each line */
282 bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */
283 int width = view_width_get(view);
285 for (Line *l = view_lines_first(view); l; l = l->next) {
286 if (l->lineno != lineno) {
287 line_cols = 0;
288 line_cc_set = false;
289 lineno = l->lineno;
292 if (line_cc_set)
293 continue;
294 line_cols += width;
296 /* This screen line contains the cell we want to highlight */
297 if (line_cols >= cc) {
298 l->cells[(cc - 1) % width].style = style;
299 line_cc_set = true;
304 static void window_draw_cursorline(Win *win) {
305 Vis *vis = win->vis;
306 View *view = win->view;
307 enum UiOption options = view_options_get(view);
308 if (!(options & UI_OPTION_CURSOR_LINE))
309 return;
310 if (vis->mode->visual || vis->win != win)
311 return;
312 if (view_selections_count(view) > 1)
313 return;
315 int width = view_width_get(view);
316 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_CURSOR_LINE);
317 Selection *sel = view_selections_primary_get(view);
318 size_t lineno = view_cursors_line_get(sel)->lineno;
319 for (Line *l = view_lines_first(view); l; l = l->next) {
320 if (l->lineno == lineno) {
321 for (int x = 0; x < width; x++) {
322 l->cells[x].style.attr |= style.attr;
323 l->cells[x].style.bg = style.bg;
325 } else if (l->lineno > lineno) {
326 break;
331 static void window_draw_selection(View *view, Selection *cur, CellStyle *style) {
332 Filerange sel = view_selections_get(cur);
333 if (!text_range_valid(&sel))
334 return;
335 Line *start_line; int start_col;
336 Line *end_line; int end_col;
337 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
338 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
339 if (!start_line && !end_line)
340 return;
341 if (!start_line) {
342 start_line = view_lines_first(view);
343 start_col = 0;
345 if (!end_line) {
346 end_line = view_lines_last(view);
347 end_col = end_line->width;
349 for (Line *l = start_line; l != end_line->next; l = l->next) {
350 int col = (l == start_line) ? start_col : 0;
351 int end = (l == end_line) ? end_col : l->width;
352 while (col < end) {
353 if (cell_color_equal(l->cells[col].style.fg, style->bg)) {
354 CellStyle old = l->cells[col].style;
355 if (!cell_color_equal(old.fg, old.bg)) {
356 l->cells[col].style.fg = old.bg;
357 l->cells[col].style.bg = old.fg;
358 } else {
359 l->cells[col].style.attr = style->attr;
361 } else {
362 l->cells[col].style.bg = style->bg;
364 col++;
369 static void window_draw_cursor_matching(Win *win, Selection *cur, CellStyle *style) {
370 if (win->vis->mode->visual)
371 return;
372 Line *line_match; int col_match;
373 size_t pos = view_cursors_pos(cur);
374 size_t pos_match = text_bracket_match_symbol(win->file->text, pos, "(){}[]\"'`");
375 if (pos == pos_match)
376 return;
377 if (!view_coord_get(win->view, pos_match, &line_match, NULL, &col_match))
378 return;
379 if (cell_color_equal(line_match->cells[col_match].style.fg, style->fg)) {
380 CellStyle old = line_match->cells[col_match].style;
381 line_match->cells[col_match].style.fg = old.bg;
382 line_match->cells[col_match].style.bg = old.fg;
383 } else {
384 line_match->cells[col_match].style.bg = style->bg;
388 static void window_draw_cursor(Win *win, Selection *cur, CellStyle *style, CellStyle *sel_style) {
389 if (win->vis->win != win)
390 return;
391 Line *line = view_cursors_line_get(cur);
392 int col = view_cursors_cell_get(cur);
393 if (!line || col == -1)
394 return;
395 line->cells[col].style = *style;
396 window_draw_cursor_matching(win, cur, sel_style);
397 return;
400 static void window_draw_selections(Win *win) {
401 View *view = win->view;
402 Filerange viewport = view_viewport_get(view);
403 bool multiple_cursors = view_selections_count(view) > 1;
404 Selection *sel = view_selections_primary_get(view);
405 CellStyle style_cursor = win->ui->style_get(win->ui, UI_STYLE_CURSOR);
406 CellStyle style_cursor_primary = win->ui->style_get(win->ui, UI_STYLE_CURSOR_PRIMARY);
407 CellStyle style_selection = win->ui->style_get(win->ui, UI_STYLE_SELECTION);
408 for (Selection *s = view_selections_prev(sel); s; s = view_selections_prev(s)) {
409 window_draw_selection(win->view, s, &style_selection);
410 size_t pos = view_cursors_pos(s);
411 if (pos < viewport.start)
412 break;
413 window_draw_cursor(win, s, &style_cursor, &style_selection);
415 window_draw_selection(win->view, sel, &style_selection);
416 window_draw_cursor(win, sel, multiple_cursors ? &style_cursor_primary : &style_cursor, &style_selection);
417 for (Selection *s = view_selections_next(sel); s; s = view_selections_next(s)) {
418 window_draw_selection(win->view, s, &style_selection);
419 size_t pos = view_cursors_pos(s);
420 if (pos > viewport.end)
421 break;
422 window_draw_cursor(win, s, &style_cursor, &style_selection);
426 static void window_draw_eof(Win *win) {
427 View *view = win->view;
428 if (view_width_get(view) == 0)
429 return;
430 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_EOF);
431 for (Line *l = view_lines_last(view)->next; l; l = l->next) {
432 strncpy(l->cells[0].data, view_symbol_eof_get(view), sizeof(l->cells[0].data)-1);
433 l->cells[0].style = style;
437 void vis_window_draw(Win *win) {
438 if (!win->ui || !view_update(win->view))
439 return;
440 Vis *vis = win->vis;
441 vis_event_emit(vis, VIS_EVENT_WIN_HIGHLIGHT, win);
443 window_draw_colorcolumn(win);
444 window_draw_cursorline(win);
445 if (!vis->win || vis->win == win || vis->win->parent == win)
446 window_draw_selections(win);
447 window_draw_eof(win);
449 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, win);
453 void vis_window_invalidate(Win *win) {
454 for (Win *w = win->vis->windows; w; w = w->next) {
455 if (w->file == win->file)
456 view_draw(w->view);
460 Win *window_new_file(Vis *vis, File *file, enum UiOption options) {
461 Win *win = calloc(1, sizeof(Win));
462 if (!win)
463 return NULL;
464 win->vis = vis;
465 win->file = file;
466 win->view = view_new(file->text);
467 win->ui = vis->ui->window_new(vis->ui, win, options);
468 if (!win->view || !win->ui) {
469 window_free(win);
470 return NULL;
472 marklist_init(&win->jumplist, 32);
473 mark_init(&win->saved_selections);
474 file->refcount++;
475 view_options_set(win->view, view_options_get(win->view));
476 view_tabwidth_set(win->view, vis->tabwidth);
478 if (vis->windows)
479 vis->windows->prev = win;
480 win->next = vis->windows;
481 vis->windows = win;
482 vis->win = win;
483 vis->ui->window_focus(win->ui);
484 for (size_t i = 0; i < LENGTH(win->modes); i++)
485 win->modes[i].parent = &vis_modes[i];
486 vis_event_emit(vis, VIS_EVENT_WIN_OPEN, win);
487 return win;
490 bool vis_window_reload(Win *win) {
491 const char *name = win->file->name;
492 if (!name)
493 return false; /* can't reload unsaved file */
494 /* temporarily unset file name, otherwise file_new returns the same File */
495 win->file->name = NULL;
496 File *file = file_new(win->vis, name);
497 win->file->name = name;
498 if (!file)
499 return false;
500 file_free(win->vis, win->file);
501 file->refcount = 1;
502 win->file = file;
503 view_reload(win->view, file->text);
504 return true;
507 bool vis_window_split(Win *original) {
508 Win *win = window_new_file(original->vis, original->file, UI_OPTION_STATUSBAR);
509 if (!win)
510 return false;
511 for (size_t i = 0; i < LENGTH(win->modes); i++) {
512 if (original->modes[i].bindings)
513 win->modes[i].bindings = map_new();
514 if (win->modes[i].bindings)
515 map_copy(win->modes[i].bindings, original->modes[i].bindings);
517 win->file = original->file;
518 view_options_set(win->view, view_options_get(original->view));
519 view_cursor_to(win->view, view_cursor_get(original->view));
520 return true;
523 void vis_window_focus(Win *win) {
524 if (!win)
525 return;
526 Vis *vis = win->vis;
527 vis->win = win;
528 vis->ui->window_focus(win->ui);
531 void vis_window_next(Vis *vis) {
532 Win *sel = vis->win;
533 if (!sel)
534 return;
535 vis_window_focus(sel->next ? sel->next : vis->windows);
538 void vis_window_prev(Vis *vis) {
539 Win *sel = vis->win;
540 if (!sel)
541 return;
542 sel = sel->prev;
543 if (!sel)
544 for (sel = vis->windows; sel->next; sel = sel->next);
545 vis_window_focus(sel);
548 int vis_window_width_get(const Win *win) {
549 return win->ui->window_width(win->ui);
552 int vis_window_height_get(const Win *win) {
553 return win->ui->window_height(win->ui);
556 void vis_draw(Vis *vis) {
557 for (Win *win = vis->windows; win; win = win->next)
558 view_draw(win->view);
561 void vis_redraw(Vis *vis) {
562 vis->ui->redraw(vis->ui);
563 vis_update(vis);
566 void vis_update(Vis *vis) {
567 vis->ui->draw(vis->ui);
570 void vis_suspend(Vis *vis) {
571 vis->ui->suspend(vis->ui);
574 void vis_resume(Vis *vis) {
575 vis->ui->resume(vis->ui);
578 bool vis_window_new(Vis *vis, const char *filename) {
579 File *file = file_new(vis, filename);
580 if (!file)
581 return false;
582 Win *win = window_new_file(vis, file, UI_OPTION_STATUSBAR|UI_OPTION_SYMBOL_EOF);
583 if (!win) {
584 file_free(vis, file);
585 return false;
588 return true;
591 bool vis_window_new_fd(Vis *vis, int fd) {
592 if (fd == -1)
593 return false;
594 if (!vis_window_new(vis, NULL))
595 return false;
596 vis->win->file->fd = fd;
597 return true;
600 bool vis_window_closable(Win *win) {
601 if (!win || !text_modified(win->file->text))
602 return true;
603 return win->file->refcount > 1;
606 void vis_window_swap(Win *a, Win *b) {
607 if (a == b || !a || !b)
608 return;
609 Vis *vis = a->vis;
610 Win *tmp = a->next;
611 a->next = b->next;
612 b->next = tmp;
613 if (a->next)
614 a->next->prev = a;
615 if (b->next)
616 b->next->prev = b;
617 tmp = a->prev;
618 a->prev = b->prev;
619 b->prev = tmp;
620 if (a->prev)
621 a->prev->next = a;
622 if (b->prev)
623 b->prev->next = b;
624 if (vis->windows == a)
625 vis->windows = b;
626 else if (vis->windows == b)
627 vis->windows = a;
628 vis->ui->window_swap(a->ui, b->ui);
629 if (vis->win == a)
630 vis_window_focus(b);
631 else if (vis->win == b)
632 vis_window_focus(a);
635 void vis_window_close(Win *win) {
636 if (!win)
637 return;
638 Vis *vis = win->vis;
639 vis_event_emit(vis, VIS_EVENT_WIN_CLOSE, win);
640 file_free(vis, win->file);
641 if (win->prev)
642 win->prev->next = win->next;
643 if (win->next)
644 win->next->prev = win->prev;
645 if (vis->windows == win)
646 vis->windows = win->next;
647 if (vis->win == win)
648 vis->win = win->next ? win->next : win->prev;
649 if (win == vis->message_window)
650 vis->message_window = NULL;
651 window_free(win);
652 if (vis->win)
653 vis->ui->window_focus(vis->win->ui);
654 vis_draw(vis);
657 Vis *vis_new(Ui *ui, VisEvent *event) {
658 if (!ui)
659 return NULL;
660 Vis *vis = calloc(1, sizeof(Vis));
661 if (!vis)
662 return NULL;
663 vis->exit_status = -1;
664 vis->ui = ui;
665 vis->tabwidth = 8;
666 vis->expandtab = false;
667 vis->change_colors = true;
668 for (size_t i = 0; i < LENGTH(vis->registers); i++)
669 register_init(&vis->registers[i]);
670 vis->registers[VIS_REG_BLACKHOLE].type = REGISTER_BLACKHOLE;
671 vis->registers[VIS_REG_CLIPBOARD].type = REGISTER_CLIPBOARD;
672 vis->registers[VIS_REG_NUMBER].type = REGISTER_NUMBER;
673 array_init(&vis->operators);
674 array_init(&vis->motions);
675 array_init(&vis->textobjects);
676 array_init(&vis->bindings);
677 array_init(&vis->actions_user);
678 action_reset(&vis->action);
679 buffer_init(&vis->input_queue);
680 if (!(vis->command_file = file_new_internal(vis, NULL)))
681 goto err;
682 if (!(vis->search_file = file_new_internal(vis, NULL)))
683 goto err;
684 if (!(vis->error_file = file_new_internal(vis, NULL)))
685 goto err;
686 if (!(vis->actions = map_new()))
687 goto err;
688 if (!(vis->keymap = map_new()))
689 goto err;
690 if (!sam_init(vis))
691 goto err;
692 struct passwd *pw;
693 char *shell = getenv("SHELL");
694 if ((!shell || !*shell) && (pw = getpwuid(getuid())))
695 shell = pw->pw_shell;
696 if (!shell || !*shell)
697 shell = "/bin/sh";
698 if (!(vis->shell = strdup(shell)))
699 goto err;
700 vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL];
701 vis->event = event;
702 if (event) {
703 if (event->mode_insert_input)
704 vis_modes[VIS_MODE_INSERT].input = event->mode_insert_input;
705 if (event->mode_replace_input)
706 vis_modes[VIS_MODE_REPLACE].input = event->mode_replace_input;
708 return vis;
709 err:
710 vis_free(vis);
711 return NULL;
714 void vis_free(Vis *vis) {
715 if (!vis)
716 return;
717 vis_event_emit(vis, VIS_EVENT_QUIT);
718 vis->event = NULL;
719 while (vis->windows)
720 vis_window_close(vis->windows);
721 file_free(vis, vis->command_file);
722 file_free(vis, vis->search_file);
723 file_free(vis, vis->error_file);
724 for (int i = 0; i < LENGTH(vis->registers); i++)
725 register_release(&vis->registers[i]);
726 vis->ui->free(vis->ui);
727 if (vis->usercmds) {
728 const char *name;
729 while (map_first(vis->usercmds, &name) && vis_cmd_unregister(vis, name));
731 map_free(vis->usercmds);
732 map_free(vis->cmds);
733 if (vis->options) {
734 const char *name;
735 while (map_first(vis->options, &name) && vis_option_unregister(vis, name));
737 map_free(vis->options);
738 map_free(vis->actions);
739 map_free(vis->keymap);
740 buffer_release(&vis->input_queue);
741 for (int i = 0; i < VIS_MODE_INVALID; i++)
742 map_free(vis_modes[i].bindings);
743 array_release_full(&vis->operators);
744 array_release_full(&vis->motions);
745 array_release_full(&vis->textobjects);
746 while (array_length(&vis->bindings))
747 vis_binding_free(vis, array_get_ptr(&vis->bindings, 0));
748 array_release(&vis->bindings);
749 while (array_length(&vis->actions_user))
750 vis_action_free(vis, array_get_ptr(&vis->actions_user, 0));
751 array_release(&vis->actions_user);
752 free(vis->shell);
753 free(vis);
756 void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) {
757 text_insert(vis->win->file->text, pos, data, len);
758 vis_window_invalidate(vis->win);
761 void vis_insert_key(Vis *vis, const char *data, size_t len) {
762 for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) {
763 size_t pos = view_cursors_pos(s);
764 vis_insert(vis, pos, data, len);
765 view_cursors_scroll_to(s, pos + len);
769 void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) {
770 Text *txt = vis->win->file->text;
771 Iterator it = text_iterator_get(txt, pos);
772 int chars = text_char_count(data, len);
773 for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\n'; )
774 text_iterator_char_next(&it, NULL);
776 text_delete(txt, pos, it.pos - pos);
777 vis_insert(vis, pos, data, len);
780 void vis_replace_key(Vis *vis, const char *data, size_t len) {
781 for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) {
782 size_t pos = view_cursors_pos(s);
783 vis_replace(vis, pos, data, len);
784 view_cursors_scroll_to(s, pos + len);
788 void vis_delete(Vis *vis, size_t pos, size_t len) {
789 text_delete(vis->win->file->text, pos, len);
790 vis_window_invalidate(vis->win);
793 bool vis_action_register(Vis *vis, const KeyAction *action) {
794 return map_put(vis->actions, action->name, action);
797 bool vis_keymap_add(Vis *vis, const char *key, const char *mapping) {
798 return map_put(vis->keymap, key, mapping);
801 void vis_keymap_disable(Vis *vis) {
802 vis->keymap_disabled = true;
805 void vis_interrupt(Vis *vis) {
806 vis->interrupted = true;
809 bool vis_interrupt_requested(Vis *vis) {
810 return vis->interrupted;
813 void vis_do(Vis *vis) {
814 Win *win = vis->win;
815 File *file = win->file;
816 Text *txt = file->text;
817 View *view = win->view;
818 Action *a = &vis->action;
820 int count = MAX(a->count, 1);
821 if (a->op == &vis_operators[VIS_OP_MODESWITCH])
822 count = 1; /* count should apply to inserted text not motion */
823 bool repeatable = a->op && !vis->macro_operator && !vis->win->parent;
824 bool multiple_cursors = view_selections_count(view) > 1;
825 bool linewise = !(a->type & CHARWISE) && (
826 a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) ||
827 vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]);
830 Register *reg = a->reg;
831 size_t reg_slot = multiple_cursors ? EPOS : 0;
832 size_t last_reg_slot = reg_slot;
833 if (!reg)
834 reg = &vis->registers[file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT];
835 if (a->op == &vis_operators[VIS_OP_PUT_AFTER] && multiple_cursors && vis_register_count(vis, reg) == 1)
836 reg_slot = 0;
838 if (vis->mode->visual && a->op)
839 window_selection_save(win);
841 for (Selection *sel = view_selections(view), *next; sel; sel = next) {
842 if (vis->interrupted)
843 break;
845 next = view_selections_next(sel);
847 size_t pos = view_cursors_pos(sel);
848 if (pos == EPOS) {
849 if (!view_selections_dispose(sel))
850 view_cursors_to(sel, 0);
851 continue;
854 OperatorContext c = {
855 .count = count,
856 .pos = pos,
857 .newpos = EPOS,
858 .range = text_range_empty(),
859 .reg = reg,
860 .reg_slot = reg_slot == EPOS ? (size_t)view_selections_number(sel) : reg_slot,
861 .linewise = linewise,
862 .arg = &a->arg,
863 .context = a->op ? a->op->context : NULL,
866 last_reg_slot = c.reg_slot;
868 bool err = false;
869 if (a->movement) {
870 size_t start = pos;
871 for (int i = 0; i < count; i++) {
872 size_t pos_prev = pos;
873 if (a->movement->txt)
874 pos = a->movement->txt(txt, pos);
875 else if (a->movement->cur)
876 pos = a->movement->cur(sel);
877 else if (a->movement->file)
878 pos = a->movement->file(vis, file, sel);
879 else if (a->movement->vis)
880 pos = a->movement->vis(vis, txt, pos);
881 else if (a->movement->view)
882 pos = a->movement->view(vis, view);
883 else if (a->movement->win)
884 pos = a->movement->win(vis, win, pos);
885 else if (a->movement->user)
886 pos = a->movement->user(vis, win, a->movement->data, pos);
887 if (pos == EPOS || a->movement->type & IDEMPOTENT || pos == pos_prev) {
888 err = a->movement->type & COUNT_EXACT;
889 break;
893 if (err) {
894 repeatable = false;
895 continue; // break?
898 if (pos == EPOS) {
899 c.range.start = start;
900 c.range.end = start;
901 pos = start;
902 } else {
903 c.range = text_range_new(start, pos);
904 c.newpos = pos;
907 if (!a->op) {
908 if (a->movement->type & CHARWISE)
909 view_cursors_scroll_to(sel, pos);
910 else
911 view_cursors_to(sel, pos);
912 if (vis->mode->visual)
913 c.range = view_selections_get(sel);
914 } else if (a->movement->type & INCLUSIVE && c.range.end > start) {
915 c.range.end = text_char_next(txt, c.range.end);
916 } else if (linewise && (a->movement->type & LINEWISE_INCLUSIVE)) {
917 c.range.end = text_char_next(txt, c.range.end);
919 } else if (a->textobj) {
920 if (vis->mode->visual)
921 c.range = view_selections_get(sel);
922 else
923 c.range.start = c.range.end = pos;
924 for (int i = 0; i < count; i++) {
925 Filerange r = text_range_empty();
926 if (a->textobj->txt)
927 r = a->textobj->txt(txt, pos);
928 else if (a->textobj->vis)
929 r = a->textobj->vis(vis, txt, pos);
930 else if (a->textobj->user)
931 r = a->textobj->user(vis, win, a->textobj->data, pos);
932 if (!text_range_valid(&r))
933 break;
934 if (a->textobj->type & TEXTOBJECT_DELIMITED_OUTER) {
935 r.start--;
936 r.end++;
939 if (vis->mode->visual || (i > 0 && !(a->textobj->type & TEXTOBJECT_NON_CONTIGUOUS)))
940 c.range = text_range_union(&c.range, &r);
941 else
942 c.range = r;
944 if (i < count - 1) {
945 if (a->textobj->type & TEXTOBJECT_EXTEND_BACKWARD) {
946 pos = c.range.start;
947 if ((a->textobj->type & TEXTOBJECT_DELIMITED_INNER) && pos > 0)
948 pos--;
949 } else {
950 pos = c.range.end;
951 if (a->textobj->type & TEXTOBJECT_DELIMITED_INNER)
952 pos++;
956 } else if (vis->mode->visual) {
957 c.range = view_selections_get(sel);
958 if (!text_range_valid(&c.range))
959 c.range.start = c.range.end = pos;
962 if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL])
963 c.range = text_range_linewise(txt, &c.range);
964 if (vis->mode->visual) {
965 view_selections_set(sel, &c.range);
966 view_selections_anchor(sel, true);
969 if (a->op) {
970 size_t pos = a->op->func(vis, txt, &c);
971 if (pos == EPOS) {
972 view_selections_dispose(sel);
973 } else if (pos <= text_size(txt)) {
974 view_selection_clear(sel);
975 view_cursors_to(sel, pos);
980 view_selections_normalize(view);
981 if (a->movement && (a->movement->type & JUMP))
982 vis_jumplist_save(vis);
984 if (a->op) {
986 if (a->op == &vis_operators[VIS_OP_YANK] ||
987 a->op == &vis_operators[VIS_OP_DELETE] ||
988 a->op == &vis_operators[VIS_OP_CHANGE] ||
989 a->op == &vis_operators[VIS_OP_REPLACE]) {
990 register_resize(reg, last_reg_slot+1);
993 /* we do not support visual repeat, still do something resonable */
994 if (vis->mode->visual && !a->movement && !a->textobj)
995 a->movement = &vis_motions[VIS_MOVE_NOP];
997 /* operator implementations must not change the mode,
998 * they might get called multiple times (once for every cursor)
1000 if (a->op == &vis_operators[VIS_OP_CHANGE]) {
1001 vis_mode_switch(vis, VIS_MODE_INSERT);
1002 } else if (a->op == &vis_operators[VIS_OP_MODESWITCH]) {
1003 vis_mode_switch(vis, a->mode);
1004 } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) {
1005 mode_set(vis, vis->mode_prev);
1006 } else if (vis->mode->visual) {
1007 vis_mode_switch(vis, VIS_MODE_NORMAL);
1010 if (vis->mode == &vis_modes[VIS_MODE_NORMAL])
1011 vis_file_snapshot(vis, file);
1012 vis_draw(vis);
1015 if (a != &vis->action_prev) {
1016 if (repeatable) {
1017 if (!a->macro)
1018 a->macro = vis->macro_operator;
1019 vis->action_prev = *a;
1021 action_reset(a);
1025 void action_reset(Action *a) {
1026 memset(a, 0, sizeof(*a));
1027 a->count = VIS_COUNT_UNKNOWN;
1030 void vis_cancel(Vis *vis) {
1031 action_reset(&vis->action);
1034 void vis_die(Vis *vis, const char *msg, ...) {
1035 va_list ap;
1036 va_start(ap, msg);
1037 vis->ui->die(vis->ui, msg, ap);
1038 va_end(ap);
1041 const char *vis_keys_next(Vis *vis, const char *keys) {
1042 if (!keys || !*keys)
1043 return NULL;
1044 TermKeyKey key;
1045 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1046 const char *next = NULL;
1047 /* first try to parse a special key of the form <Key> */
1048 if (*keys == '<' && keys[1] && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1049 return next+1;
1050 if (strncmp(keys, "<vis-", 5) == 0) {
1051 const char *start = keys + 1, *end = start;
1052 while (*end && *end != '>')
1053 end++;
1054 if (end > start && end - start - 1 < VIS_KEY_LENGTH_MAX && *end == '>') {
1055 char key[VIS_KEY_LENGTH_MAX];
1056 memcpy(key, start, end - start);
1057 key[end - start] = '\0';
1058 if (map_get(vis->actions, key))
1059 return end + 1;
1062 if (ISUTF8(*keys))
1063 keys++;
1064 while (!ISUTF8(*keys))
1065 keys++;
1066 return keys;
1069 long vis_keys_codepoint(Vis *vis, const char *keys) {
1070 long codepoint = -1;
1071 const char *next;
1072 TermKeyKey key;
1073 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1075 if (!keys[0])
1076 return -1;
1077 if (keys[0] == '<' && !keys[1])
1078 return '<';
1080 if (keys[0] == '<' && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1081 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1082 else if ((next = termkey_strpkey(termkey, keys, &key, TERMKEY_FORMAT_VIM)))
1083 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1085 if (codepoint != -1) {
1086 if (key.modifiers == TERMKEY_KEYMOD_CTRL)
1087 codepoint &= 0x1f;
1088 return codepoint;
1091 if (!next || key.type != TERMKEY_TYPE_KEYSYM)
1092 return -1;
1094 const int keysym[] = {
1095 TERMKEY_SYM_ENTER, '\r',
1096 TERMKEY_SYM_TAB, '\t',
1097 TERMKEY_SYM_BACKSPACE, '\b',
1098 TERMKEY_SYM_ESCAPE, 0x1b,
1099 TERMKEY_SYM_DELETE, 0x7f,
1103 for (const int *k = keysym; k[0]; k += 2) {
1104 if (key.code.sym == k[0])
1105 return k[1];
1108 return -1;
1111 bool vis_keys_utf8(Vis *vis, const char *keys, char utf8[static UTFmax+1]) {
1112 Rune rune = vis_keys_codepoint(vis, keys);
1113 if (rune == (Rune)-1)
1114 return false;
1115 size_t len = runetochar(utf8, &rune);
1116 utf8[len] = '\0';
1117 return true;
1120 typedef struct {
1121 Vis *vis;
1122 size_t len; // length of the prefix
1123 int count; // how many bindings can complete this prefix
1124 bool angle_bracket; // does the prefix end with '<'
1125 } PrefixCompletion;
1127 static bool isprefix(const char *key, void *value, void *data) {
1128 PrefixCompletion *completion = data;
1129 if (!completion->angle_bracket) {
1130 completion->count++;
1131 } else {
1132 const char *start = key + completion->len;
1133 const char *end = vis_keys_next(completion->vis, start);
1134 if (end && start + 1 == end)
1135 completion->count++;
1137 return completion->count == 1;
1140 static void vis_keys_process(Vis *vis, size_t pos) {
1141 Buffer *buf = &vis->input_queue;
1142 char *keys = buf->data + pos, *start = keys, *cur = keys, *end = keys, *binding_end = keys;;
1143 bool prefix = false;
1144 KeyBinding *binding = NULL;
1146 while (cur && *cur) {
1148 if (!(end = (char*)vis_keys_next(vis, cur))) {
1149 buffer_remove(buf, keys - buf->data, strlen(keys));
1150 return;
1153 char tmp = *end;
1154 *end = '\0';
1155 prefix = false;
1157 for (Mode *global_mode = vis->mode; global_mode && !prefix; global_mode = global_mode->parent) {
1158 for (int global = 0; global < 2 && !prefix; global++) {
1159 Mode *mode = (global || !vis->win) ?
1160 global_mode :
1161 &vis->win->modes[global_mode->id];
1162 if (!mode->bindings)
1163 continue;
1164 /* keep track of longest matching binding */
1165 KeyBinding *match = map_get(mode->bindings, start);
1166 if (match && end > binding_end) {
1167 binding = match;
1168 binding_end = end;
1171 const Map *pmap = map_prefix(mode->bindings, start);
1172 PrefixCompletion completions = {
1173 .vis = vis,
1174 .len = cur - start,
1175 .count = 0,
1176 .angle_bracket = !strcmp(cur, "<"),
1178 map_iterate(pmap, isprefix, &completions);
1180 prefix = (!match && completions.count > 0) ||
1181 ( match && completions.count > 1);
1185 *end = tmp;
1187 if (prefix) {
1188 /* input sofar is ambigious, wait for more */
1189 cur = end;
1190 end = start;
1191 } else if (binding) { /* exact match */
1192 if (binding->action) {
1193 size_t len = binding_end - start;
1194 strcpy(vis->key_prev, vis->key_current);
1195 strncpy(vis->key_current, start, len);
1196 vis->key_current[len] = '\0';
1197 end = (char*)binding->action->func(vis, binding_end, &binding->action->arg);
1198 if (!end) {
1199 end = start;
1200 break;
1202 start = cur = end;
1203 } else if (binding->alias) {
1204 buffer_remove(buf, start - buf->data, binding_end - start);
1205 buffer_insert0(buf, start - buf->data, binding->alias);
1206 cur = end = start;
1208 binding = NULL;
1209 binding_end = start;
1210 } else { /* no keybinding */
1211 KeyAction *action = NULL;
1212 if (start[0] == '<' && end[-1] == '>') {
1213 /* test for special editor key command */
1214 char tmp = end[-1];
1215 end[-1] = '\0';
1216 action = map_get(vis->actions, start+1);
1217 end[-1] = tmp;
1218 if (action) {
1219 size_t len = end - start;
1220 strcpy(vis->key_prev, vis->key_current);
1221 strncpy(vis->key_current, start, len);
1222 vis->key_current[len] = '\0';
1223 end = (char*)action->func(vis, end, &action->arg);
1224 if (!end) {
1225 end = start;
1226 break;
1230 if (!action && vis->mode->input) {
1231 end = (char*)vis_keys_next(vis, start);
1232 vis->mode->input(vis, start, end - start);
1234 start = cur = end;
1238 buffer_remove(buf, keys - buf->data, end - keys);
1241 void vis_keys_feed(Vis *vis, const char *input) {
1242 if (!input)
1243 return;
1244 Macro macro;
1245 macro_init(&macro);
1246 if (!macro_append(&macro, input))
1247 return;
1248 /* use internal function, to keep Lua based tests which use undo points working */
1249 macro_replay_internal(vis, &macro);
1250 macro_release(&macro);
1253 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record) {
1254 if (!input)
1255 return;
1256 if (record && vis->recording)
1257 macro_append(vis->recording, input);
1258 if (vis->macro_operator)
1259 macro_append(vis->macro_operator, input);
1260 if (buffer_append0(&vis->input_queue, input))
1261 vis_keys_process(vis, pos);
1264 static const char *getkey(Vis *vis) {
1265 TermKeyKey key = { 0 };
1266 if (!vis->ui->getkey(vis->ui, &key))
1267 return NULL;
1268 vis_info_hide(vis);
1269 bool use_keymap = vis->mode->id != VIS_MODE_INSERT &&
1270 vis->mode->id != VIS_MODE_REPLACE &&
1271 !vis->keymap_disabled;
1272 vis->keymap_disabled = false;
1273 if (key.type == TERMKEY_TYPE_UNICODE && use_keymap) {
1274 const char *mapped = map_get(vis->keymap, key.utf8);
1275 if (mapped) {
1276 size_t len = strlen(mapped)+1;
1277 if (len <= sizeof(key.utf8))
1278 memcpy(key.utf8, mapped, len);
1282 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1283 termkey_strfkey(termkey, vis->key, sizeof(vis->key), &key, TERMKEY_FORMAT_VIM);
1284 return vis->key;
1287 bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const void *context) {
1288 switch (signum) {
1289 case SIGBUS:
1290 for (File *file = vis->files; file; file = file->next) {
1291 if (text_mmaped(file->text, siginfo->si_addr))
1292 file->truncated = true;
1294 vis->sigbus = true;
1295 if (vis->running)
1296 siglongjmp(vis->sigbus_jmpbuf, 1);
1297 return true;
1298 case SIGINT:
1299 vis->interrupted = true;
1300 return true;
1301 case SIGCONT:
1302 vis->resume = true;
1303 /* fall through */
1304 case SIGWINCH:
1305 vis->need_resize = true;
1306 return true;
1307 case SIGTERM:
1308 case SIGHUP:
1309 vis->terminate = true;
1310 return true;
1312 return false;
1315 int vis_run(Vis *vis) {
1316 if (!vis->windows)
1317 return EXIT_SUCCESS;
1318 if (vis->exit_status != -1)
1319 return vis->exit_status;
1320 vis->running = true;
1322 vis_event_emit(vis, VIS_EVENT_START);
1324 struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL;
1326 sigset_t emptyset;
1327 sigemptyset(&emptyset);
1328 vis_draw(vis);
1329 vis->exit_status = EXIT_SUCCESS;
1331 sigsetjmp(vis->sigbus_jmpbuf, 1);
1333 while (vis->running) {
1334 fd_set fds;
1335 FD_ZERO(&fds);
1336 FD_SET(STDIN_FILENO, &fds);
1338 if (vis->sigbus) {
1339 char *name = NULL;
1340 for (Win *next, *win = vis->windows; win; win = next) {
1341 next = win->next;
1342 if (win->file->truncated) {
1343 free(name);
1344 name = strdup(win->file->name);
1345 vis_window_close(win);
1348 if (!vis->windows)
1349 vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1350 else
1351 vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1352 vis->sigbus = false;
1353 free(name);
1356 if (vis->terminate)
1357 vis_die(vis, "Killed by SIGTERM\n");
1358 if (vis->interrupted) {
1359 vis->interrupted = false;
1360 vis_keys_feed(vis, "<C-c>");
1363 if (vis->resume) {
1364 vis_resume(vis);
1365 vis->resume = false;
1368 if (vis->need_resize) {
1369 vis->ui->resize(vis->ui);
1370 vis->need_resize = false;
1373 vis_update(vis);
1374 idle.tv_sec = vis->mode->idle_timeout;
1375 int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
1376 if (r == -1 && errno == EINTR)
1377 continue;
1379 if (r < 0) {
1380 /* TODO save all pending changes to a ~suffixed file */
1381 vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
1384 if (!FD_ISSET(STDIN_FILENO, &fds)) {
1385 if (vis->mode->idle)
1386 vis->mode->idle(vis);
1387 timeout = NULL;
1388 continue;
1391 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1392 termkey_advisereadable(termkey);
1393 const char *key;
1395 while ((key = getkey(vis)))
1396 vis_keys_push(vis, key, 0, true);
1398 if (vis->mode->idle)
1399 timeout = &idle;
1401 return vis->exit_status;
1404 Macro *macro_get(Vis *vis, enum VisRegister id) {
1405 if (id == VIS_MACRO_LAST_RECORDED)
1406 return vis->last_recording;
1407 if (VIS_REG_A <= id && id <= VIS_REG_Z)
1408 id -= VIS_REG_A;
1409 if (id < LENGTH(vis->registers))
1410 return array_get(&vis->registers[id].values, 0);
1411 return NULL;
1414 void macro_operator_record(Vis *vis) {
1415 if (vis->macro_operator)
1416 return;
1417 vis->macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
1418 macro_reset(vis->macro_operator);
1421 void macro_operator_stop(Vis *vis) {
1422 if (!vis->macro_operator)
1423 return;
1424 Macro *dot = macro_get(vis, VIS_REG_DOT);
1425 buffer_put(dot, vis->macro_operator->data, vis->macro_operator->len);
1426 vis->action_prev.macro = dot;
1427 vis->macro_operator = NULL;
1430 bool vis_macro_record(Vis *vis, enum VisRegister id) {
1431 Macro *macro = macro_get(vis, id);
1432 if (vis->recording || !macro)
1433 return false;
1434 if (!(VIS_REG_A <= id && id <= VIS_REG_Z))
1435 macro_reset(macro);
1436 vis->recording = macro;
1437 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1438 return true;
1441 bool vis_macro_record_stop(Vis *vis) {
1442 if (!vis->recording)
1443 return false;
1444 /* XXX: hack to remove last recorded key, otherwise upon replay
1445 * we would start another recording */
1446 if (vis->recording->len > 1) {
1447 vis->recording->len--;
1448 vis->recording->data[vis->recording->len-1] = '\0';
1450 vis->last_recording = vis->recording;
1451 vis->recording = NULL;
1452 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1453 return true;
1456 bool vis_macro_recording(Vis *vis) {
1457 return vis->recording;
1460 static void macro_replay(Vis *vis, const Macro *macro) {
1461 const Macro *replaying = vis->replaying;
1462 vis->replaying = macro;
1463 macro_replay_internal(vis, macro);
1464 vis->replaying = replaying;
1467 static void macro_replay_internal(Vis *vis, const Macro *macro) {
1468 size_t pos = buffer_length0(&vis->input_queue);
1469 for (char *key = macro->data, *next; key; key = next) {
1470 char tmp;
1471 next = (char*)vis_keys_next(vis, key);
1472 if (next) {
1473 tmp = *next;
1474 *next = '\0';
1477 vis_keys_push(vis, key, pos, false);
1479 if (next)
1480 *next = tmp;
1484 bool vis_macro_replay(Vis *vis, enum VisRegister id) {
1485 if (id == VIS_REG_SEARCH)
1486 return vis_motion(vis, VIS_MOVE_SEARCH_REPEAT_FORWARD);
1487 if (id == VIS_REG_COMMAND) {
1488 const char *cmd = register_get(vis, &vis->registers[id], NULL);
1489 return vis_cmd(vis, cmd);
1492 Macro *macro = macro_get(vis, id);
1493 if (!macro || macro == vis->recording)
1494 return false;
1495 int count = vis_count_get_default(vis, 1);
1496 vis_cancel(vis);
1497 for (int i = 0; i < count; i++)
1498 macro_replay(vis, macro);
1499 vis_file_snapshot(vis, vis->win->file);
1500 return true;
1503 void vis_repeat(Vis *vis) {
1504 const Macro *macro = vis->action_prev.macro;
1505 int count = vis->action.count;
1506 if (count != VIS_COUNT_UNKNOWN)
1507 vis->action_prev.count = count;
1508 else
1509 count = vis->action_prev.count;
1510 vis->action = vis->action_prev;
1511 vis_do(vis);
1512 if (macro) {
1513 Mode *mode = vis->mode;
1514 Action action_prev = vis->action_prev;
1515 if (count < 1 || action_prev.op == &vis_operators[VIS_OP_CHANGE])
1516 count = 1;
1517 if (vis->action_prev.op == &vis_operators[VIS_OP_MODESWITCH])
1518 vis->action_prev.count = 1;
1519 for (int i = 0; i < count; i++) {
1520 if (vis->interrupted)
1521 break;
1522 mode_set(vis, mode);
1523 macro_replay(vis, macro);
1525 vis->action_prev = action_prev;
1527 vis_cancel(vis);
1528 vis_file_snapshot(vis, vis->win->file);
1531 int vis_count_get(Vis *vis) {
1532 return vis->action.count;
1535 int vis_count_get_default(Vis *vis, int def) {
1536 if (vis->action.count == VIS_COUNT_UNKNOWN)
1537 return def;
1538 return vis->action.count;
1541 void vis_count_set(Vis *vis, int count) {
1542 vis->action.count = (count >= 0 ? count : VIS_COUNT_UNKNOWN);
1545 VisCountIterator vis_count_iterator_get(Vis *vis, int def) {
1546 return (VisCountIterator) {
1547 .vis = vis,
1548 .iteration = 0,
1549 .count = vis_count_get_default(vis, def),
1553 VisCountIterator vis_count_iterator_init(Vis *vis, int count) {
1554 return (VisCountIterator) {
1555 .vis = vis,
1556 .iteration = 0,
1557 .count = count,
1561 bool vis_count_iterator_next(VisCountIterator *it) {
1562 if (it->vis->interrupted)
1563 return false;
1564 return it->iteration++ < it->count;
1567 void vis_exit(Vis *vis, int status) {
1568 vis->running = false;
1569 vis->exit_status = status;
1572 void vis_insert_tab(Vis *vis) {
1573 if (!vis->expandtab) {
1574 vis_insert_key(vis, "\t", 1);
1575 return;
1577 char spaces[9];
1578 int tabwidth = MIN(vis->tabwidth, LENGTH(spaces) - 1);
1579 for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) {
1580 size_t pos = view_cursors_pos(s);
1581 int width = text_line_width_get(vis->win->file->text, pos);
1582 int count = tabwidth - (width % tabwidth);
1583 for (int i = 0; i < count; i++)
1584 spaces[i] = ' ';
1585 spaces[count] = '\0';
1586 vis_insert(vis, pos, spaces, count);
1587 view_cursors_scroll_to(s, pos + count);
1591 size_t vis_text_insert_nl(Vis *vis, Text *txt, size_t pos) {
1592 size_t indent_len = 0;
1593 char byte, *indent = NULL;
1594 /* insert second newline at end of file, except if there is already one */
1595 bool eof = pos == text_size(txt);
1596 bool nl2 = eof && !(pos > 0 && text_byte_get(txt, pos-1, &byte) && byte == '\n');
1598 if (vis->autoindent) {
1599 /* copy leading white space of current line */
1600 size_t begin = text_line_begin(txt, pos);
1601 size_t start = text_line_start(txt, begin);
1602 size_t end = text_line_end(txt, start);
1603 if (start > pos)
1604 start = pos;
1605 indent_len = start >= begin ? start-begin : 0;
1606 if (start == end) {
1607 pos = begin;
1608 } else {
1609 indent = malloc(indent_len+1);
1610 if (indent)
1611 indent_len = text_bytes_get(txt, begin, indent_len, indent);
1615 text_insert(txt, pos, "\n", 1);
1616 if (eof) {
1617 if (nl2)
1618 text_insert(txt, text_size(txt), "\n", 1);
1619 else
1620 pos--; /* place cursor before, not after nl */
1622 pos++;
1624 if (indent)
1625 text_insert(txt, pos, indent, indent_len);
1626 free(indent);
1627 return pos + indent_len;
1630 void vis_insert_nl(Vis *vis) {
1631 Win *win = vis->win;
1632 View *view = win->view;
1633 Text *txt = win->file->text;
1634 for (Selection *s = view_selections(view); s; s = view_selections_next(s)) {
1635 size_t pos = view_cursors_pos(s);
1636 size_t newpos = vis_text_insert_nl(vis, txt, pos);
1637 /* This is a bit of a hack to fix cursor positioning when
1638 * inserting a new line at the start of the view port.
1639 * It has the effect of reseting the mark used by the view
1640 * code to keep track of the start of the visible region.
1642 view_cursors_to(s, pos);
1643 view_cursors_to(s, newpos);
1645 vis_window_invalidate(win);
1648 Regex *vis_regex(Vis *vis, const char *pattern) {
1649 if (!pattern && !(pattern = register_get(vis, &vis->registers[VIS_REG_SEARCH], NULL)))
1650 return NULL;
1651 Regex *regex = text_regex_new();
1652 if (!regex)
1653 return NULL;
1654 if (text_regex_compile(regex, pattern, REG_EXTENDED|REG_NEWLINE) != 0) {
1655 text_regex_free(regex);
1656 return NULL;
1658 register_put0(vis, &vis->registers[VIS_REG_SEARCH], pattern);
1659 return regex;
1662 int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
1663 void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
1664 void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len)) {
1666 /* if an invalid range was given, stdin (i.e. key board input) is passed
1667 * through the external command. */
1668 Text *text = file->text;
1669 int pin[2], pout[2], perr[2], status = -1;
1670 bool interactive = !text_range_valid(range);
1671 Filerange rout = interactive ? text_range_new(0, 0) : *range;
1673 if (pipe(pin) == -1)
1674 return -1;
1675 if (pipe(pout) == -1) {
1676 close(pin[0]);
1677 close(pin[1]);
1678 return -1;
1681 if (pipe(perr) == -1) {
1682 close(pin[0]);
1683 close(pin[1]);
1684 close(pout[0]);
1685 close(pout[1]);
1686 return -1;
1689 vis->ui->terminal_save(vis->ui);
1690 pid_t pid = fork();
1692 if (pid == -1) {
1693 close(pin[0]);
1694 close(pin[1]);
1695 close(pout[0]);
1696 close(pout[1]);
1697 close(perr[0]);
1698 close(perr[1]);
1699 vis_info_show(vis, "fork failure: %s", strerror(errno));
1700 return -1;
1701 } else if (pid == 0) { /* child i.e filter */
1702 sigset_t sigterm_mask;
1703 sigemptyset(&sigterm_mask);
1704 sigaddset(&sigterm_mask, SIGTERM);
1705 if (sigprocmask(SIG_UNBLOCK, &sigterm_mask, NULL) == -1) {
1706 fprintf(stderr, "failed to reset signal mask");
1707 exit(EXIT_FAILURE);
1710 int null = open("/dev/null", O_RDWR);
1711 if (null == -1) {
1712 fprintf(stderr, "failed to open /dev/null");
1713 exit(EXIT_FAILURE);
1716 if (!interactive) {
1717 /* If we have nothing to write, let stdin point to
1718 * /dev/null instead of a pipe which is immediately
1719 * closed. Some programs behave differently when used
1720 * in a pipeline.
1722 if (text_range_size(range) == 0)
1723 dup2(null, STDIN_FILENO);
1724 else
1725 dup2(pin[0], STDIN_FILENO);
1728 close(pin[0]);
1729 close(pin[1]);
1730 if (interactive) {
1731 dup2(STDERR_FILENO, STDOUT_FILENO);
1732 /* For some reason the first byte written by the
1733 * interactive application is not being displayed.
1734 * It probably has something to do with the terminal
1735 * state change. By writing a dummy byte ourself we
1736 * ensure that the complete output is visible.
1738 while(write(STDOUT_FILENO, " ", 1) == -1 && errno == EINTR);
1739 } else if (read_stdout) {
1740 dup2(pout[1], STDOUT_FILENO);
1741 } else {
1742 dup2(null, STDOUT_FILENO);
1744 close(pout[1]);
1745 close(pout[0]);
1746 if (!interactive) {
1747 if (read_stderr)
1748 dup2(perr[1], STDERR_FILENO);
1749 else
1750 dup2(null, STDERR_FILENO);
1752 close(perr[0]);
1753 close(perr[1]);
1754 close(null);
1756 if (file->name) {
1757 char *name = strrchr(file->name, '/');
1758 setenv("vis_filepath", file->name, 1);
1759 setenv("vis_filename", name ? name+1 : file->name, 1);
1762 if (!argv[1])
1763 execlp(vis->shell, vis->shell, "-c", argv[0], (char*)NULL);
1764 else
1765 execvp(argv[0], (char* const*)argv);
1766 fprintf(stderr, "exec failure: %s", strerror(errno));
1767 exit(EXIT_FAILURE);
1770 vis->interrupted = false;
1772 close(pin[0]);
1773 close(pout[1]);
1774 close(perr[1]);
1776 if (fcntl(pout[0], F_SETFL, O_NONBLOCK) == -1 ||
1777 fcntl(perr[0], F_SETFL, O_NONBLOCK) == -1)
1778 goto err;
1780 fd_set rfds, wfds;
1782 do {
1783 if (vis->interrupted) {
1784 kill(0, SIGTERM);
1785 break;
1788 FD_ZERO(&rfds);
1789 FD_ZERO(&wfds);
1790 if (pin[1] != -1)
1791 FD_SET(pin[1], &wfds);
1792 if (pout[0] != -1)
1793 FD_SET(pout[0], &rfds);
1794 if (perr[0] != -1)
1795 FD_SET(perr[0], &rfds);
1797 if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) {
1798 if (errno == EINTR)
1799 continue;
1800 vis_info_show(vis, "Select failure");
1801 break;
1804 if (pin[1] != -1 && FD_ISSET(pin[1], &wfds)) {
1805 Filerange junk = rout;
1806 if (junk.end > junk.start + PIPE_BUF)
1807 junk.end = junk.start + PIPE_BUF;
1808 ssize_t len = text_write_range(text, &junk, pin[1]);
1809 if (len > 0) {
1810 rout.start += len;
1811 if (text_range_size(&rout) == 0) {
1812 close(pout[1]);
1813 pout[1] = -1;
1815 } else {
1816 close(pin[1]);
1817 pin[1] = -1;
1818 if (len == -1)
1819 vis_info_show(vis, "Error writing to external command");
1823 if (pout[0] != -1 && FD_ISSET(pout[0], &rfds)) {
1824 char buf[BUFSIZ];
1825 ssize_t len = read(pout[0], buf, sizeof buf);
1826 if (len > 0) {
1827 if (read_stdout)
1828 (*read_stdout)(stdout_context, buf, len);
1829 } else if (len == 0) {
1830 close(pout[0]);
1831 pout[0] = -1;
1832 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1833 vis_info_show(vis, "Error reading from filter stdout");
1834 close(pout[0]);
1835 pout[0] = -1;
1839 if (perr[0] != -1 && FD_ISSET(perr[0], &rfds)) {
1840 char buf[BUFSIZ];
1841 ssize_t len = read(perr[0], buf, sizeof buf);
1842 if (len > 0) {
1843 if (read_stderr)
1844 (*read_stderr)(stderr_context, buf, len);
1845 } else if (len == 0) {
1846 close(perr[0]);
1847 perr[0] = -1;
1848 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1849 vis_info_show(vis, "Error reading from filter stderr");
1850 close(perr[0]);
1851 perr[0] = -1;
1855 } while (pin[1] != -1 || pout[0] != -1 || perr[0] != -1);
1857 err:
1858 if (pin[1] != -1)
1859 close(pin[1]);
1860 if (pout[0] != -1)
1861 close(pout[0]);
1862 if (perr[0] != -1)
1863 close(perr[0]);
1865 for (;;) {
1866 if (vis->interrupted)
1867 kill(0, SIGTERM);
1868 pid_t died = waitpid(pid, &status, 0);
1869 if ((died == -1 && errno == ECHILD) || pid == died)
1870 break;
1873 /* clear any pending SIGTERM */
1874 struct sigaction sigterm_ignore, sigterm_old;
1875 sigterm_ignore.sa_handler = SIG_IGN;
1876 sigterm_ignore.sa_flags = 0;
1877 sigemptyset(&sigterm_ignore.sa_mask);
1879 sigaction(SIGTERM, &sigterm_ignore, &sigterm_old);
1880 sigaction(SIGTERM, &sigterm_old, NULL);
1882 vis->interrupted = false;
1883 vis->ui->terminal_restore(vis->ui);
1885 return status;
1888 static ssize_t read_buffer(void *context, char *data, size_t len) {
1889 buffer_append(context, data, len);
1890 return len;
1893 int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[], char **out, char **err) {
1894 Buffer bufout, buferr;
1895 buffer_init(&bufout);
1896 buffer_init(&buferr);
1897 int status = vis_pipe(vis, file, range, argv,
1898 &bufout, out ? read_buffer : NULL,
1899 &buferr, err ? read_buffer : NULL);
1900 buffer_terminate(&bufout);
1901 buffer_terminate(&buferr);
1902 if (out)
1903 *out = buffer_move(&bufout);
1904 if (err)
1905 *err = buffer_move(&buferr);
1906 buffer_release(&bufout);
1907 buffer_release(&buferr);
1908 return status;
1911 bool vis_cmd(Vis *vis, const char *cmdline) {
1912 if (!cmdline)
1913 return true;
1914 while (*cmdline == ':')
1915 cmdline++;
1916 size_t len = strlen(cmdline);
1917 char *line = malloc(len+2);
1918 if (!line)
1919 return false;
1920 strncpy(line, cmdline, len+1);
1922 for (char *end = line + len - 1; end >= line && isspace((unsigned char)*end); end--)
1923 *end = '\0';
1925 enum SamError err = sam_cmd(vis, line);
1926 if (err != SAM_ERR_OK)
1927 vis_info_show(vis, "%s", sam_error(err));
1928 free(line);
1929 return err == SAM_ERR_OK;
1932 void vis_file_snapshot(Vis *vis, File *file) {
1933 if (!vis->replaying)
1934 text_snapshot(file->text);
1937 Text *vis_text(Vis *vis) {
1938 return vis->win->file->text;
1941 View *vis_view(Vis *vis) {
1942 return vis->win->view;
1945 Win *vis_window(Vis *vis) {
1946 return vis->win;
1949 bool vis_get_autoindent(const Vis *vis) {
1950 return vis->autoindent;