vis: implement intersection of selections
[vis.git] / vis.c
blobdf29941c283fac89b086029dc5dc2ccb7d97cf15
1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <signal.h>
6 #include <stdarg.h>
7 #include <stdio.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <limits.h>
11 #include <ctype.h>
12 #include <time.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <sys/stat.h>
17 #include <sys/ioctl.h>
18 #include <sys/mman.h>
19 #include <pwd.h>
20 #include <libgen.h>
21 #include <termkey.h>
23 #include "vis.h"
24 #include "text-util.h"
25 #include "text-motions.h"
26 #include "text-objects.h"
27 #include "util.h"
28 #include "vis-core.h"
29 #include "sam.h"
31 const MarkDef vis_marks[] = {
32 [VIS_MARK_SELECTION_START] = { '<', VIS_HELP("Last selection start") },
33 [VIS_MARK_SELECTION_END] = { '>', VIS_HELP("Last selection end") },
36 static void macro_replay(Vis *vis, const Macro *macro);
37 static void macro_replay_internal(Vis *vis, const Macro *macro);
38 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record);
40 bool vis_event_emit(Vis *vis, enum VisEvents id, ...) {
41 if (!vis->event)
42 return true;
44 if (!vis->initialized) {
45 vis->initialized = true;
46 vis->ui->init(vis->ui, vis);
47 if (vis->event->init)
48 vis->event->init(vis);
51 va_list ap;
52 va_start(ap, id);
53 bool ret = true;
55 switch (id) {
56 case VIS_EVENT_INIT:
57 break;
58 case VIS_EVENT_START:
59 if (vis->event->start)
60 vis->event->start(vis);
61 break;
62 case VIS_EVENT_FILE_OPEN:
63 case VIS_EVENT_FILE_SAVE_PRE:
64 case VIS_EVENT_FILE_SAVE_POST:
65 case VIS_EVENT_FILE_CLOSE:
67 File *file = va_arg(ap, File*);
68 if (file->internal)
69 break;
70 if (id == VIS_EVENT_FILE_OPEN && vis->event->file_open) {
71 vis->event->file_open(vis, file);
72 } else if (id == VIS_EVENT_FILE_SAVE_PRE && vis->event->file_save_pre) {
73 const char *path = va_arg(ap, const char*);
74 ret = vis->event->file_save_pre(vis, file, path);
75 } else if (id == VIS_EVENT_FILE_SAVE_POST && vis->event->file_save_post) {
76 const char *path = va_arg(ap, const char*);
77 vis->event->file_save_post(vis, file, path);
78 } else if (id == VIS_EVENT_FILE_CLOSE && vis->event->file_close) {
79 vis->event->file_close(vis, file);
81 break;
83 case VIS_EVENT_WIN_OPEN:
84 case VIS_EVENT_WIN_CLOSE:
85 case VIS_EVENT_WIN_HIGHLIGHT:
86 case VIS_EVENT_WIN_STATUS:
88 Win *win = va_arg(ap, Win*);
89 if (win->file->internal && id != VIS_EVENT_WIN_STATUS)
90 break;
91 if (vis->event->win_open && id == VIS_EVENT_WIN_OPEN) {
92 vis->event->win_open(vis, win);
93 } else if (vis->event->win_close && id == VIS_EVENT_WIN_CLOSE) {
94 vis->event->win_close(vis, win);
95 } else if (vis->event->win_highlight && id == VIS_EVENT_WIN_HIGHLIGHT) {
96 vis->event->win_highlight(vis, win);
97 } else if (vis->event->win_status && id == VIS_EVENT_WIN_STATUS) {
98 vis->event->win_status(vis, win);
100 break;
102 case VIS_EVENT_QUIT:
103 if (vis->event->quit)
104 vis->event->quit(vis);
105 break;
108 va_end(ap);
109 return ret;
112 /** window / file handling */
114 static void file_free(Vis *vis, File *file) {
115 if (!file)
116 return;
117 if (file->refcount > 1) {
118 --file->refcount;
119 return;
121 vis_event_emit(vis, VIS_EVENT_FILE_CLOSE, file);
122 text_free(file->text);
123 free((char*)file->name);
125 if (file->prev)
126 file->prev->next = file->next;
127 if (file->next)
128 file->next->prev = file->prev;
129 if (vis->files == file)
130 vis->files = file->next;
131 free(file);
134 static File *file_new_text(Vis *vis, Text *text) {
135 File *file = calloc(1, sizeof(*file));
136 if (!file)
137 return NULL;
138 file->fd = -1;
139 file->text = text;
140 file->stat = text_stat(text);
141 if (vis->files)
142 vis->files->prev = file;
143 file->next = vis->files;
144 vis->files = file;
145 return file;
148 static char *absolute_path(const char *name) {
149 if (!name)
150 return NULL;
151 char *copy1 = strdup(name);
152 char *copy2 = strdup(name);
153 char *path_absolute = NULL;
154 char path_normalized[PATH_MAX] = "";
156 if (!copy1 || !copy2)
157 goto err;
159 char *dir = dirname(copy1);
160 char *base = basename(copy2);
161 if (!(path_absolute = realpath(dir, NULL)))
162 goto err;
163 if (strcmp(path_absolute, "/") == 0)
164 path_absolute[0] = '\0';
166 snprintf(path_normalized, sizeof(path_normalized), "%s/%s",
167 path_absolute, base);
168 err:
169 free(copy1);
170 free(copy2);
171 free(path_absolute);
172 return path_normalized[0] ? strdup(path_normalized) : NULL;
175 static File *file_new(Vis *vis, const char *name) {
176 char *name_absolute = NULL;
177 if (name) {
178 if (!(name_absolute = absolute_path(name)))
179 return NULL;
180 File *existing = NULL;
181 /* try to detect whether the same file is already open in another window
182 * TODO: do this based on inodes */
183 for (File *file = vis->files; file; file = file->next) {
184 if (file->name && strcmp(file->name, name_absolute) == 0) {
185 existing = file;
186 break;
189 if (existing) {
190 free(name_absolute);
191 return existing;
195 File *file = NULL;
196 Text *text = text_load(name);
197 if (!text && name && errno == ENOENT)
198 text = text_load(NULL);
199 if (!text)
200 goto err;
201 if (!(file = file_new_text(vis, text)))
202 goto err;
203 file->name = name_absolute;
204 vis_event_emit(vis, VIS_EVENT_FILE_OPEN, file);
205 return file;
206 err:
207 free(name_absolute);
208 text_free(text);
209 file_free(vis, file);
210 return NULL;
213 static File *file_new_internal(Vis *vis, const char *filename) {
214 File *file = file_new(vis, filename);
215 if (file) {
216 file->refcount = 1;
217 file->internal = true;
219 return file;
222 void file_name_set(File *file, const char *name) {
223 if (name == file->name)
224 return;
225 free((char*)file->name);
226 file->name = absolute_path(name);
229 const char *file_name_get(File *file) {
230 /* TODO: calculate path relative to working directory, cache result */
231 if (!file->name)
232 return NULL;
233 char cwd[PATH_MAX];
234 if (!getcwd(cwd, sizeof cwd))
235 return file->name;
236 const char *path = strstr(file->name, cwd);
237 if (path != file->name)
238 return file->name;
239 size_t cwdlen = strlen(cwd);
240 return file->name[cwdlen] == '/' ? file->name+cwdlen+1 : file->name;
243 void vis_window_status(Win *win, const char *status) {
244 win->ui->status(win->ui, status);
247 void window_selection_save(Win *win) {
248 Vis *vis = win->vis;
249 File *file = win->file;
250 Filerange sel = view_selections_get(view_selections(win->view));
251 file->marks[VIS_MARK_SELECTION_START] = text_mark_set(file->text, sel.start);
252 file->marks[VIS_MARK_SELECTION_END] = text_mark_set(file->text, sel.end);
253 if (!vis->action.op) {
254 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s))
255 view_selections_save(s);
259 static void window_free(Win *win) {
260 if (!win)
261 return;
262 Vis *vis = win->vis;
263 for (Win *other = vis->windows; other; other = other->next) {
264 if (other->parent == win)
265 other->parent = NULL;
267 if (vis->ui)
268 vis->ui->window_free(win->ui);
269 view_free(win->view);
270 for (size_t i = 0; i < LENGTH(win->modes); i++)
271 map_free(win->modes[i].bindings);
272 ringbuf_free(win->jumplist);
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 lineno = l->lineno;
294 if (line_cc_set)
295 continue;
296 line_cols += width;
298 /* This screen line contains the cell we want to highlight */
299 if (line_cols >= cc) {
300 l->cells[(cc - 1) % width].style = style;
301 line_cc_set = true;
306 static void window_draw_cursorline(Win *win) {
307 Vis *vis = win->vis;
308 View *view = win->view;
309 enum UiOption options = view_options_get(view);
310 if (!(options & UI_OPTION_CURSOR_LINE))
311 return;
312 if (vis->mode->visual || vis->win != win)
313 return;
314 if (view_selections_count(view) > 1)
315 return;
317 int width = view_width_get(view);
318 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_CURSOR_LINE);
319 Selection *sel = view_selections_primary_get(view);
320 size_t lineno = view_cursors_line_get(sel)->lineno;
321 for (Line *l = view_lines_first(view); l; l = l->next) {
322 if (l->lineno == lineno) {
323 for (int x = 0; x < width; x++) {
324 l->cells[x].style.attr |= style.attr;
325 l->cells[x].style.bg = style.bg;
327 } else if (l->lineno > lineno) {
328 break;
333 static void window_draw_selection(View *view, Selection *cur, CellStyle *style) {
334 Filerange sel = view_selections_get(cur);
335 if (!text_range_valid(&sel))
336 return;
337 Line *start_line; int start_col;
338 Line *end_line; int end_col;
339 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
340 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
341 if (!start_line && !end_line)
342 return;
343 if (!start_line) {
344 start_line = view_lines_first(view);
345 start_col = 0;
347 if (!end_line) {
348 end_line = view_lines_last(view);
349 end_col = end_line->width;
351 for (Line *l = start_line; l != end_line->next; l = l->next) {
352 int col = (l == start_line) ? start_col : 0;
353 int end = (l == end_line) ? end_col : l->width;
354 while (col < end) {
355 if (cell_color_equal(l->cells[col].style.fg, style->bg)) {
356 CellStyle old = l->cells[col].style;
357 l->cells[col].style.fg = old.bg;
358 l->cells[col].style.bg = old.fg;
359 } else {
360 l->cells[col].style.bg = style->bg;
362 col++;
367 static void window_draw_cursor_matching(Win *win, Selection *cur, CellStyle *style) {
368 if (win->vis->mode->visual)
369 return;
370 Line *line_match; int col_match;
371 size_t pos = view_cursors_pos(cur);
372 size_t pos_match = text_bracket_match_symbol(win->file->text, pos, "(){}[]\"'`");
373 if (pos == pos_match)
374 return;
375 if (!view_coord_get(win->view, pos_match, &line_match, NULL, &col_match))
376 return;
377 if (cell_color_equal(line_match->cells[col_match].style.fg, style->fg)) {
378 CellStyle old = line_match->cells[col_match].style;
379 line_match->cells[col_match].style.fg = old.bg;
380 line_match->cells[col_match].style.bg = old.fg;
381 } else {
382 line_match->cells[col_match].style.bg = style->bg;
386 static void window_draw_cursor(Win *win, Selection *cur, CellStyle *style, CellStyle *sel_style) {
387 if (win->vis->win != win)
388 return;
389 Line *line = view_cursors_line_get(cur);
390 int col = view_cursors_cell_get(cur);
391 if (!line || col == -1)
392 return;
393 line->cells[col].style = *style;
394 window_draw_cursor_matching(win, cur, sel_style);
395 return;
398 static void window_draw_cursors(Win *win) {
399 View *view = win->view;
400 Filerange viewport = view_viewport_get(view);
401 bool multiple_cursors = view_selections_count(view) > 1;
402 Selection *sel = view_selections_primary_get(view);
403 CellStyle style_cursor = win->ui->style_get(win->ui, UI_STYLE_CURSOR);
404 CellStyle style_cursor_primary = win->ui->style_get(win->ui, UI_STYLE_CURSOR_PRIMARY);
405 CellStyle style_selection = win->ui->style_get(win->ui, UI_STYLE_SELECTION);
406 for (Selection *s = view_selections_prev(sel); s; s = view_selections_prev(s)) {
407 window_draw_selection(win->view, s, &style_selection);
408 size_t pos = view_cursors_pos(s);
409 if (pos < viewport.start)
410 break;
411 window_draw_cursor(win, s, &style_cursor, &style_selection);
413 window_draw_selection(win->view, sel, &style_selection);
414 window_draw_cursor(win, sel, multiple_cursors ? &style_cursor_primary : &style_cursor, &style_selection);
415 for (Selection *s = view_selections_next(sel); s; s = view_selections_next(s)) {
416 window_draw_selection(win->view, s, &style_selection);
417 size_t pos = view_cursors_pos(s);
418 if (pos > viewport.end)
419 break;
420 window_draw_cursor(win, s, &style_cursor, &style_selection);
424 static void window_draw_eof(Win *win) {
425 View *view = win->view;
426 if (view_width_get(view) == 0)
427 return;
428 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_EOF);
429 for (Line *l = view_lines_last(view)->next; l; l = l->next) {
430 strcpy(l->cells[0].data, "~");
431 l->cells[0].style = style;
435 void vis_window_draw(Win *win) {
436 if (!win->ui || !view_update(win->view))
437 return;
438 Vis *vis = win->vis;
439 vis_event_emit(vis, VIS_EVENT_WIN_HIGHLIGHT, win);
441 window_draw_colorcolumn(win);
442 window_draw_cursorline(win);
443 window_draw_cursors(win);
444 window_draw_eof(win);
446 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, win);
450 void vis_window_invalidate(Win *win) {
451 for (Win *w = win->vis->windows; w; w = w->next) {
452 if (w->file == win->file)
453 view_draw(w->view);
457 Win *window_new_file(Vis *vis, File *file, enum UiOption options) {
458 Win *win = calloc(1, sizeof(Win));
459 if (!win)
460 return NULL;
461 win->vis = vis;
462 win->file = file;
463 win->jumplist = ringbuf_alloc(31);
464 win->view = view_new(file->text);
465 win->ui = vis->ui->window_new(vis->ui, win, options);
466 if (!win->jumplist || !win->view || !win->ui) {
467 window_free(win);
468 return NULL;
470 file->refcount++;
471 view_tabwidth_set(win->view, vis->tabwidth);
473 if (vis->windows)
474 vis->windows->prev = win;
475 win->next = vis->windows;
476 vis->windows = win;
477 vis->win = win;
478 vis->ui->window_focus(win->ui);
479 for (size_t i = 0; i < LENGTH(win->modes); i++)
480 win->modes[i].parent = &vis_modes[i];
481 vis_event_emit(vis, VIS_EVENT_WIN_OPEN, win);
482 return win;
485 bool vis_window_reload(Win *win) {
486 const char *name = win->file->name;
487 if (!name)
488 return false; /* can't reload unsaved file */
489 /* temporarily unset file name, otherwise file_new returns the same File */
490 win->file->name = NULL;
491 File *file = file_new(win->vis, name);
492 win->file->name = name;
493 if (!file)
494 return false;
495 file_free(win->vis, win->file);
496 file->refcount = 1;
497 win->file = file;
498 view_reload(win->view, file->text);
499 return true;
502 bool vis_window_split(Win *original) {
503 Win *win = window_new_file(original->vis, original->file, UI_OPTION_STATUSBAR);
504 if (!win)
505 return false;
506 for (size_t i = 0; i < LENGTH(win->modes); i++) {
507 if (original->modes[i].bindings)
508 win->modes[i].bindings = map_new();
509 if (win->modes[i].bindings)
510 map_copy(win->modes[i].bindings, original->modes[i].bindings);
512 win->file = original->file;
513 view_options_set(win->view, view_options_get(original->view));
514 view_cursor_to(win->view, view_cursor_get(original->view));
515 return true;
518 void vis_window_focus(Win *win) {
519 if (!win)
520 return;
521 Vis *vis = win->vis;
522 vis->win = win;
523 vis->ui->window_focus(win->ui);
526 void vis_window_next(Vis *vis) {
527 Win *sel = vis->win;
528 if (!sel)
529 return;
530 vis_window_focus(sel->next ? sel->next : vis->windows);
533 void vis_window_prev(Vis *vis) {
534 Win *sel = vis->win;
535 if (!sel)
536 return;
537 sel = sel->prev;
538 if (!sel)
539 for (sel = vis->windows; sel->next; sel = sel->next);
540 vis_window_focus(sel);
543 int vis_window_width_get(const Win *win) {
544 return win->ui->window_width(win->ui);
547 int vis_window_height_get(const Win *win) {
548 return win->ui->window_height(win->ui);
551 void vis_draw(Vis *vis) {
552 for (Win *win = vis->windows; win; win = win->next)
553 view_draw(win->view);
556 void vis_redraw(Vis *vis) {
557 vis->ui->redraw(vis->ui);
558 vis_update(vis);
561 void vis_update(Vis *vis) {
562 vis->ui->draw(vis->ui);
565 void vis_suspend(Vis *vis) {
566 vis->ui->suspend(vis->ui);
569 void vis_resume(Vis *vis) {
570 vis->ui->resume(vis->ui);
573 bool vis_window_new(Vis *vis, const char *filename) {
574 File *file = file_new(vis, filename);
575 if (!file)
576 return false;
577 Win *win = window_new_file(vis, file, UI_OPTION_STATUSBAR);
578 if (!win) {
579 file_free(vis, file);
580 return false;
583 return true;
586 bool vis_window_new_fd(Vis *vis, int fd) {
587 if (fd == -1)
588 return false;
589 if (!vis_window_new(vis, NULL))
590 return false;
591 vis->win->file->fd = fd;
592 return true;
595 bool vis_window_closable(Win *win) {
596 if (!win || !text_modified(win->file->text))
597 return true;
598 return win->file->refcount > 1;
601 void vis_window_swap(Win *a, Win *b) {
602 if (a == b || !a || !b)
603 return;
604 Vis *vis = a->vis;
605 Win *tmp = a->next;
606 a->next = b->next;
607 b->next = tmp;
608 if (a->next)
609 a->next->prev = a;
610 if (b->next)
611 b->next->prev = b;
612 tmp = a->prev;
613 a->prev = b->prev;
614 b->prev = tmp;
615 if (a->prev)
616 a->prev->next = a;
617 if (b->prev)
618 b->prev->next = b;
619 if (vis->windows == a)
620 vis->windows = b;
621 else if (vis->windows == b)
622 vis->windows = a;
623 vis->ui->window_swap(a->ui, b->ui);
624 if (vis->win == a)
625 vis_window_focus(b);
626 else if (vis->win == b)
627 vis_window_focus(a);
630 void vis_window_close(Win *win) {
631 if (!win)
632 return;
633 Vis *vis = win->vis;
634 vis_event_emit(vis, VIS_EVENT_WIN_CLOSE, win);
635 file_free(vis, win->file);
636 if (win->prev)
637 win->prev->next = win->next;
638 if (win->next)
639 win->next->prev = win->prev;
640 if (vis->windows == win)
641 vis->windows = win->next;
642 if (vis->win == win)
643 vis->win = win->next ? win->next : win->prev;
644 if (win == vis->message_window)
645 vis->message_window = NULL;
646 window_free(win);
647 if (vis->win)
648 vis->ui->window_focus(vis->win->ui);
649 vis_draw(vis);
652 Vis *vis_new(Ui *ui, VisEvent *event) {
653 if (!ui)
654 return NULL;
655 Vis *vis = calloc(1, sizeof(Vis));
656 if (!vis)
657 return NULL;
658 vis->exit_status = -1;
659 vis->ui = ui;
660 vis->tabwidth = 8;
661 vis->expandtab = false;
662 vis->change_colors = true;
663 for (size_t i = 0; i < LENGTH(vis->registers); i++)
664 register_init(&vis->registers[i]);
665 vis->registers[VIS_REG_BLACKHOLE].type = REGISTER_BLACKHOLE;
666 vis->registers[VIS_REG_CLIPBOARD].type = REGISTER_CLIPBOARD;
667 vis->registers[VIS_REG_NUMBER].type = REGISTER_NUMBER;
668 array_init(&vis->operators);
669 array_init(&vis->motions);
670 array_init(&vis->textobjects);
671 array_init(&vis->bindings);
672 array_init(&vis->actions_user);
673 action_reset(&vis->action);
674 buffer_init(&vis->input_queue);
675 if (!(vis->command_file = file_new_internal(vis, NULL)))
676 goto err;
677 if (!(vis->search_file = file_new_internal(vis, NULL)))
678 goto err;
679 if (!(vis->error_file = file_new_internal(vis, NULL)))
680 goto err;
681 if (!(vis->actions = map_new()))
682 goto err;
683 if (!(vis->keymap = map_new()))
684 goto err;
685 if (!sam_init(vis))
686 goto err;
687 struct passwd *pw;
688 char *shell = getenv("SHELL");
689 if ((!shell || !*shell) && (pw = getpwuid(getuid())))
690 shell = pw->pw_shell;
691 if (!shell || !*shell)
692 shell = "/bin/sh";
693 if (!(vis->shell = strdup(shell)))
694 goto err;
695 vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL];
696 vis->event = event;
697 if (event) {
698 if (event->mode_insert_input)
699 vis_modes[VIS_MODE_INSERT].input = event->mode_insert_input;
700 if (event->mode_replace_input)
701 vis_modes[VIS_MODE_REPLACE].input = event->mode_replace_input;
703 return vis;
704 err:
705 vis_free(vis);
706 return NULL;
709 void vis_free(Vis *vis) {
710 if (!vis)
711 return;
712 vis_event_emit(vis, VIS_EVENT_QUIT);
713 vis->event = NULL;
714 while (vis->windows)
715 vis_window_close(vis->windows);
716 file_free(vis, vis->command_file);
717 file_free(vis, vis->search_file);
718 file_free(vis, vis->error_file);
719 for (int i = 0; i < LENGTH(vis->registers); i++)
720 register_release(&vis->registers[i]);
721 vis->ui->free(vis->ui);
722 if (vis->usercmds) {
723 const char *name;
724 while (map_first(vis->usercmds, &name) && vis_cmd_unregister(vis, name));
726 map_free(vis->usercmds);
727 map_free(vis->cmds);
728 if (vis->options) {
729 const char *name;
730 while (map_first(vis->options, &name) && vis_option_unregister(vis, name));
732 map_free(vis->options);
733 map_free(vis->actions);
734 map_free(vis->keymap);
735 buffer_release(&vis->input_queue);
736 for (int i = 0; i < VIS_MODE_INVALID; i++)
737 map_free(vis_modes[i].bindings);
738 array_release_full(&vis->operators);
739 array_release_full(&vis->motions);
740 array_release_full(&vis->textobjects);
741 while (array_length(&vis->bindings))
742 vis_binding_free(vis, array_get_ptr(&vis->bindings, 0));
743 array_release(&vis->bindings);
744 while (array_length(&vis->actions_user))
745 vis_action_free(vis, array_get_ptr(&vis->actions_user, 0));
746 array_release(&vis->actions_user);
747 free(vis->shell);
748 free(vis);
751 void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) {
752 text_insert(vis->win->file->text, pos, data, len);
753 vis_window_invalidate(vis->win);
756 void vis_insert_key(Vis *vis, const char *data, size_t len) {
757 for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) {
758 size_t pos = view_cursors_pos(s);
759 vis_insert(vis, pos, data, len);
760 view_cursors_scroll_to(s, pos + len);
764 void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) {
765 Text *txt = vis->win->file->text;
766 Iterator it = text_iterator_get(txt, pos);
767 int chars = text_char_count(data, len);
768 for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\n'; )
769 text_iterator_char_next(&it, NULL);
771 text_delete(txt, pos, it.pos - pos);
772 vis_insert(vis, pos, data, len);
775 void vis_replace_key(Vis *vis, const char *data, size_t len) {
776 for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) {
777 size_t pos = view_cursors_pos(s);
778 vis_replace(vis, pos, data, len);
779 view_cursors_scroll_to(s, pos + len);
783 void vis_delete(Vis *vis, size_t pos, size_t len) {
784 text_delete(vis->win->file->text, pos, len);
785 vis_window_invalidate(vis->win);
788 bool vis_action_register(Vis *vis, const KeyAction *action) {
789 return map_put(vis->actions, action->name, action);
792 bool vis_keymap_add(Vis *vis, const char *key, const char *mapping) {
793 return map_put(vis->keymap, key, mapping);
796 void vis_keymap_disable(Vis *vis) {
797 vis->keymap_disabled = true;
800 static void window_jumplist_add(Win *win, size_t pos) {
801 Mark mark = text_mark_set(win->file->text, pos);
802 if (mark && win->jumplist)
803 ringbuf_add(win->jumplist, (void*)mark);
806 static void window_jumplist_invalidate(Win *win) {
807 if (win->jumplist)
808 ringbuf_invalidate(win->jumplist);
811 void vis_interrupt(Vis *vis) {
812 vis->interrupted = true;
815 bool vis_interrupt_requested(Vis *vis) {
816 return vis->interrupted;
819 void vis_do(Vis *vis) {
820 Win *win = vis->win;
821 File *file = win->file;
822 Text *txt = file->text;
823 View *view = win->view;
824 Action *a = &vis->action;
826 if (a->op == &vis_operators[VIS_OP_FILTER] && !vis->mode->visual)
827 vis_mode_switch(vis, VIS_MODE_VISUAL_LINE);
829 int count = MAX(a->count, 1);
830 if (a->op == &vis_operators[VIS_OP_MODESWITCH])
831 count = 1; /* count should apply to inserted text not motion */
832 bool repeatable = a->op && !vis->macro_operator && !vis->win->parent;
833 bool multiple_cursors = view_selections_count(view) > 1;
834 bool linewise = !(a->type & CHARWISE) && (
835 a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) ||
836 vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]);
839 Register *reg = a->reg;
840 size_t reg_slot = multiple_cursors ? EPOS : 0;
841 size_t last_reg_slot = reg_slot;
842 if (!reg)
843 reg = &vis->registers[file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT];
844 if (a->op == &vis_operators[VIS_OP_PUT_AFTER] && multiple_cursors && vis_register_count(vis, reg) == 1)
845 reg_slot = 0;
847 for (Selection *sel = view_selections(view), *next; sel; sel = next) {
848 if (vis->interrupted)
849 break;
851 next = view_selections_next(sel);
853 size_t pos = view_cursors_pos(sel);
854 if (pos == EPOS) {
855 if (!view_selections_dispose(sel))
856 view_cursors_to(sel, 0);
857 continue;
860 OperatorContext c = {
861 .count = count,
862 .pos = pos,
863 .newpos = EPOS,
864 .range = text_range_empty(),
865 .reg = reg,
866 .reg_slot = reg_slot == EPOS ? (size_t)view_selections_number(sel) : reg_slot,
867 .linewise = linewise,
868 .arg = &a->arg,
869 .context = a->op ? a->op->context : NULL,
872 last_reg_slot = c.reg_slot;
874 bool err = false;
875 if (a->movement) {
876 size_t start = pos;
877 for (int i = 0; i < count; i++) {
878 size_t pos_prev = pos;
879 if (a->movement->txt)
880 pos = a->movement->txt(txt, pos);
881 else if (a->movement->cur)
882 pos = a->movement->cur(sel);
883 else if (a->movement->file)
884 pos = a->movement->file(vis, file, pos);
885 else if (a->movement->vis)
886 pos = a->movement->vis(vis, txt, pos);
887 else if (a->movement->view)
888 pos = a->movement->view(vis, view);
889 else if (a->movement->win)
890 pos = a->movement->win(vis, win, pos);
891 else if (a->movement->user)
892 pos = a->movement->user(vis, win, a->movement->data, pos);
893 if (pos == EPOS || a->movement->type & IDEMPOTENT || pos == pos_prev) {
894 err = a->movement->type & COUNT_EXACT;
895 break;
899 if (err) {
900 repeatable = false;
901 continue; // break?
904 if (pos == EPOS) {
905 c.range.start = start;
906 c.range.end = start;
907 pos = start;
908 } else {
909 c.range = text_range_new(start, pos);
910 c.newpos = pos;
913 if (!a->op) {
914 if (a->movement->type & CHARWISE)
915 view_cursors_scroll_to(sel, pos);
916 else
917 view_cursors_to(sel, pos);
918 if (vis->mode->visual)
919 c.range = view_selections_get(sel);
920 if (a->movement->type & JUMP)
921 window_jumplist_add(win, pos);
922 else
923 window_jumplist_invalidate(win);
924 } else if (a->movement->type & INCLUSIVE && c.range.end > start) {
925 c.range.end = text_char_next(txt, c.range.end);
926 } else if (linewise && (a->movement->type & LINEWISE_INCLUSIVE)) {
927 c.range.end = text_char_next(txt, c.range.end);
929 } else if (a->textobj) {
930 if (vis->mode->visual)
931 c.range = view_selections_get(sel);
932 else
933 c.range.start = c.range.end = pos;
934 for (int i = 0; i < count; i++) {
935 Filerange r = text_range_empty();
936 if (a->textobj->txt)
937 r = a->textobj->txt(txt, pos);
938 else if (a->textobj->vis)
939 r = a->textobj->vis(vis, txt, pos);
940 else if (a->textobj->user)
941 r = a->textobj->user(vis, win, a->textobj->data, pos);
942 if (!text_range_valid(&r))
943 break;
944 if (a->textobj->type & TEXTOBJECT_DELIMITED_OUTER) {
945 r.start--;
946 r.end++;
949 if (vis->mode->visual || (i > 0 && !(a->textobj->type & TEXTOBJECT_NON_CONTIGUOUS)))
950 c.range = text_range_union(&c.range, &r);
951 else
952 c.range = r;
954 if (i < count - 1) {
955 if (a->textobj->type & TEXTOBJECT_EXTEND_BACKWARD) {
956 pos = c.range.start;
957 if ((a->textobj->type & TEXTOBJECT_DELIMITED_INNER) && pos > 0)
958 pos--;
959 } else {
960 pos = c.range.end;
961 if (a->textobj->type & TEXTOBJECT_DELIMITED_INNER)
962 pos++;
966 } else if (vis->mode->visual) {
967 c.range = view_selections_get(sel);
968 if (!text_range_valid(&c.range))
969 c.range.start = c.range.end = pos;
972 if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL])
973 c.range = text_range_linewise(txt, &c.range);
974 if (vis->mode->visual) {
975 view_selections_set(sel, &c.range);
976 view_selections_anchor(sel);
979 if (a->op) {
980 size_t pos = a->op->func(vis, txt, &c);
981 if (pos == EPOS) {
982 view_selections_dispose(sel);
983 } else if (pos <= text_size(txt)) {
984 if (vis->mode->visual)
985 view_selections_save(sel);
986 view_cursors_to(sel, pos);
987 if (vis->mode->visual)
988 view_selection_clear(sel);
993 view_selections_normalize(view);
995 if (a->op) {
997 if (a->op == &vis_operators[VIS_OP_YANK] ||
998 a->op == &vis_operators[VIS_OP_DELETE] ||
999 a->op == &vis_operators[VIS_OP_CHANGE] ||
1000 a->op == &vis_operators[VIS_OP_REPLACE]) {
1001 register_resize(reg, last_reg_slot+1);
1004 /* we do not support visual repeat, still do something resonable */
1005 if (vis->mode->visual && !a->movement && !a->textobj)
1006 a->movement = &vis_motions[VIS_MOVE_NOP];
1008 /* operator implementations must not change the mode,
1009 * they might get called multiple times (once for every cursor)
1011 if (a->op == &vis_operators[VIS_OP_CHANGE]) {
1012 vis_mode_switch(vis, VIS_MODE_INSERT);
1013 } else if (a->op == &vis_operators[VIS_OP_MODESWITCH]) {
1014 vis_mode_switch(vis, a->mode);
1015 } else if (a->op == &vis_operators[VIS_OP_FILTER]) {
1016 if (a->arg.s)
1017 vis_cmd(vis, a->arg.s);
1018 else
1019 vis_prompt_show(vis, ":|");
1020 } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) {
1021 mode_set(vis, vis->mode_prev);
1022 } else if (vis->mode->visual) {
1023 vis_mode_switch(vis, VIS_MODE_NORMAL);
1026 if (vis->mode == &vis_modes[VIS_MODE_NORMAL])
1027 vis_file_snapshot(vis, file);
1028 vis_draw(vis);
1031 if (a != &vis->action_prev) {
1032 if (repeatable) {
1033 if (!a->macro)
1034 a->macro = vis->macro_operator;
1035 vis->action_prev = *a;
1037 action_reset(a);
1041 void action_reset(Action *a) {
1042 memset(a, 0, sizeof(*a));
1043 a->count = VIS_COUNT_UNKNOWN;
1046 void vis_cancel(Vis *vis) {
1047 action_reset(&vis->action);
1050 void vis_die(Vis *vis, const char *msg, ...) {
1051 va_list ap;
1052 va_start(ap, msg);
1053 vis->ui->die(vis->ui, msg, ap);
1054 va_end(ap);
1057 const char *vis_keys_next(Vis *vis, const char *keys) {
1058 if (!keys || !*keys)
1059 return NULL;
1060 TermKeyKey key;
1061 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1062 const char *next = NULL;
1063 /* first try to parse a special key of the form <Key> */
1064 if (*keys == '<' && keys[1] && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1065 return next+1;
1066 if (strncmp(keys, "<vis-", 5) == 0) {
1067 const char *start = keys + 1, *end = start;
1068 while (*end && *end != '>')
1069 end++;
1070 if (end > start && end - start - 1 < VIS_KEY_LENGTH_MAX && *end == '>') {
1071 char key[VIS_KEY_LENGTH_MAX];
1072 memcpy(key, start, end - start);
1073 key[end - start] = '\0';
1074 if (map_get(vis->actions, key))
1075 return end + 1;
1078 if (ISUTF8(*keys))
1079 keys++;
1080 while (!ISUTF8(*keys))
1081 keys++;
1082 return keys;
1085 long vis_keys_codepoint(Vis *vis, const char *keys) {
1086 long codepoint = -1;
1087 const char *next;
1088 TermKeyKey key;
1089 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1091 if (!keys[0])
1092 return -1;
1093 if (keys[0] == '<' && !keys[1])
1094 return '<';
1096 if (keys[0] == '<' && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1097 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1098 else if ((next = termkey_strpkey(termkey, keys, &key, TERMKEY_FORMAT_VIM)))
1099 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1101 if (codepoint != -1) {
1102 if (key.modifiers == TERMKEY_KEYMOD_CTRL)
1103 codepoint &= 0x1f;
1104 return codepoint;
1107 if (!next || key.type != TERMKEY_TYPE_KEYSYM)
1108 return -1;
1110 const int keysym[] = {
1111 TERMKEY_SYM_ENTER, '\n',
1112 TERMKEY_SYM_TAB, '\t',
1113 TERMKEY_SYM_BACKSPACE, '\b',
1114 TERMKEY_SYM_ESCAPE, 0x1b,
1115 TERMKEY_SYM_DELETE, 0x7f,
1119 for (const int *k = keysym; k[0]; k += 2) {
1120 if (key.code.sym == k[0])
1121 return k[1];
1124 return -1;
1127 bool vis_keys_utf8(Vis *vis, const char *keys, char utf8[static UTFmax+1]) {
1128 Rune rune = vis_keys_codepoint(vis, keys);
1129 if (rune == (Rune)-1)
1130 return false;
1131 size_t len = runetochar(utf8, &rune);
1132 utf8[len] = '\0';
1133 return true;
1136 static void vis_keys_process(Vis *vis, size_t pos) {
1137 Buffer *buf = &vis->input_queue;
1138 char *keys = buf->data + pos, *start = keys, *cur = keys, *end = keys, *binding_end = keys;;
1139 bool prefix = false;
1140 KeyBinding *binding = NULL;
1142 while (cur && *cur) {
1144 if (!(end = (char*)vis_keys_next(vis, cur))) {
1145 buffer_remove(buf, keys - buf->data, strlen(keys));
1146 return;
1149 char tmp = *end;
1150 *end = '\0';
1151 prefix = false;
1153 for (Mode *global_mode = vis->mode; global_mode && !prefix; global_mode = global_mode->parent) {
1154 for (int global = 0; global < 2 && !prefix; global++) {
1155 Mode *mode = (global || !vis->win) ?
1156 global_mode :
1157 &vis->win->modes[global_mode->id];
1158 if (!mode->bindings)
1159 continue;
1160 /* keep track of longest matching binding */
1161 KeyBinding *match = map_get(mode->bindings, start);
1162 if (match && end > binding_end) {
1163 binding = match;
1164 binding_end = end;
1166 /* "<" is never treated as a prefix because it
1167 * is used to denote special key symbols */
1168 if (strcmp(start, "<")) {
1169 prefix = (!match && map_contains(mode->bindings, start)) ||
1170 (match && !map_leaf(mode->bindings, start));
1175 *end = tmp;
1177 if (prefix) {
1178 /* input sofar is ambigious, wait for more */
1179 cur = end;
1180 end = start;
1181 } else if (binding) { /* exact match */
1182 if (binding->action) {
1183 size_t len = binding_end - start;
1184 strcpy(vis->key_prev, vis->key_current);
1185 strncpy(vis->key_current, start, len);
1186 vis->key_current[len] = '\0';
1187 end = (char*)binding->action->func(vis, binding_end, &binding->action->arg);
1188 if (!end) {
1189 end = start;
1190 break;
1192 start = cur = end;
1193 } else if (binding->alias) {
1194 buffer_remove(buf, start - buf->data, binding_end - start);
1195 buffer_insert0(buf, start - buf->data, binding->alias);
1196 cur = end = start;
1198 binding = NULL;
1199 binding_end = start;
1200 } else { /* no keybinding */
1201 KeyAction *action = NULL;
1202 if (start[0] == '<' && end[-1] == '>') {
1203 /* test for special editor key command */
1204 char tmp = end[-1];
1205 end[-1] = '\0';
1206 action = map_get(vis->actions, start+1);
1207 end[-1] = tmp;
1208 if (action) {
1209 size_t len = end - start;
1210 strcpy(vis->key_prev, vis->key_current);
1211 strncpy(vis->key_current, start, len);
1212 vis->key_current[len] = '\0';
1213 end = (char*)action->func(vis, end, &action->arg);
1214 if (!end) {
1215 end = start;
1216 break;
1220 if (!action && vis->mode->input) {
1221 end = (char*)vis_keys_next(vis, start);
1222 vis->mode->input(vis, start, end - start);
1224 start = cur = end;
1228 buffer_remove(buf, keys - buf->data, end - keys);
1231 void vis_keys_feed(Vis *vis, const char *input) {
1232 if (!input)
1233 return;
1234 Macro macro;
1235 macro_init(&macro);
1236 if (!macro_append(&macro, input))
1237 return;
1238 /* use internal function, to keep Lua based tests which use undo points working */
1239 macro_replay_internal(vis, &macro);
1240 macro_release(&macro);
1243 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record) {
1244 if (!input)
1245 return;
1246 if (record && vis->recording)
1247 macro_append(vis->recording, input);
1248 if (vis->macro_operator)
1249 macro_append(vis->macro_operator, input);
1250 if (buffer_append0(&vis->input_queue, input))
1251 vis_keys_process(vis, pos);
1254 static const char *getkey(Vis *vis) {
1255 TermKeyKey key = { 0 };
1256 if (!vis->ui->getkey(vis->ui, &key))
1257 return NULL;
1258 vis_info_hide(vis);
1259 bool use_keymap = vis->mode->id != VIS_MODE_INSERT &&
1260 vis->mode->id != VIS_MODE_REPLACE &&
1261 !vis->keymap_disabled;
1262 vis->keymap_disabled = false;
1263 if (key.type == TERMKEY_TYPE_UNICODE && use_keymap) {
1264 const char *mapped = map_get(vis->keymap, key.utf8);
1265 if (mapped) {
1266 size_t len = strlen(mapped)+1;
1267 if (len <= sizeof(key.utf8))
1268 memcpy(key.utf8, mapped, len);
1272 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1273 termkey_strfkey(termkey, vis->key, sizeof(vis->key), &key, TERMKEY_FORMAT_VIM);
1274 return vis->key;
1277 bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const void *context) {
1278 switch (signum) {
1279 case SIGBUS:
1280 for (File *file = vis->files; file; file = file->next) {
1281 if (text_mmaped(file->text, siginfo->si_addr))
1282 file->truncated = true;
1284 vis->sigbus = true;
1285 if (vis->running)
1286 siglongjmp(vis->sigbus_jmpbuf, 1);
1287 return true;
1288 case SIGINT:
1289 vis->interrupted = true;
1290 return true;
1291 case SIGCONT:
1292 vis->resume = true;
1293 /* fall through */
1294 case SIGWINCH:
1295 vis->need_resize = true;
1296 return true;
1297 case SIGTERM:
1298 case SIGHUP:
1299 vis->terminate = true;
1300 return true;
1302 return false;
1305 int vis_run(Vis *vis) {
1306 if (!vis->windows)
1307 return EXIT_SUCCESS;
1308 if (vis->exit_status != -1)
1309 return vis->exit_status;
1310 vis->running = true;
1312 vis_event_emit(vis, VIS_EVENT_START);
1314 struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL;
1316 sigset_t emptyset;
1317 sigemptyset(&emptyset);
1318 vis_draw(vis);
1319 vis->exit_status = EXIT_SUCCESS;
1321 sigsetjmp(vis->sigbus_jmpbuf, 1);
1323 while (vis->running) {
1324 fd_set fds;
1325 FD_ZERO(&fds);
1326 FD_SET(STDIN_FILENO, &fds);
1328 if (vis->sigbus) {
1329 char *name = NULL;
1330 for (Win *next, *win = vis->windows; win; win = next) {
1331 next = win->next;
1332 if (win->file->truncated) {
1333 free(name);
1334 name = strdup(win->file->name);
1335 vis_window_close(win);
1338 if (!vis->windows)
1339 vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1340 else
1341 vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1342 vis->sigbus = false;
1343 free(name);
1346 if (vis->terminate)
1347 vis_die(vis, "Killed by SIGTERM\n");
1348 if (vis->interrupted) {
1349 vis->interrupted = false;
1350 vis_keys_feed(vis, "<C-c>");
1353 if (vis->resume) {
1354 vis_resume(vis);
1355 vis->resume = false;
1358 if (vis->need_resize) {
1359 vis->ui->resize(vis->ui);
1360 vis->need_resize = false;
1363 vis_update(vis);
1364 idle.tv_sec = vis->mode->idle_timeout;
1365 int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
1366 if (r == -1 && errno == EINTR)
1367 continue;
1369 if (r < 0) {
1370 /* TODO save all pending changes to a ~suffixed file */
1371 vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
1374 if (!FD_ISSET(STDIN_FILENO, &fds)) {
1375 if (vis->mode->idle)
1376 vis->mode->idle(vis);
1377 timeout = NULL;
1378 continue;
1381 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1382 termkey_advisereadable(termkey);
1383 const char *key;
1385 while ((key = getkey(vis)))
1386 vis_keys_push(vis, key, 0, true);
1388 if (vis->mode->idle)
1389 timeout = &idle;
1391 return vis->exit_status;
1394 Macro *macro_get(Vis *vis, enum VisRegister id) {
1395 if (id == VIS_MACRO_LAST_RECORDED)
1396 return vis->last_recording;
1397 if (VIS_REG_A <= id && id <= VIS_REG_Z)
1398 id -= VIS_REG_A;
1399 if (id < LENGTH(vis->registers))
1400 return array_get(&vis->registers[id].values, 0);
1401 return NULL;
1404 void macro_operator_record(Vis *vis) {
1405 if (vis->macro_operator)
1406 return;
1407 vis->macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
1408 macro_reset(vis->macro_operator);
1411 void macro_operator_stop(Vis *vis) {
1412 if (!vis->macro_operator)
1413 return;
1414 Macro *dot = macro_get(vis, VIS_REG_DOT);
1415 buffer_put(dot, vis->macro_operator->data, vis->macro_operator->len);
1416 vis->action_prev.macro = dot;
1417 vis->macro_operator = NULL;
1420 bool vis_macro_record(Vis *vis, enum VisRegister id) {
1421 Macro *macro = macro_get(vis, id);
1422 if (vis->recording || !macro)
1423 return false;
1424 if (!(VIS_REG_A <= id && id <= VIS_REG_Z))
1425 macro_reset(macro);
1426 vis->recording = macro;
1427 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1428 return true;
1431 bool vis_macro_record_stop(Vis *vis) {
1432 if (!vis->recording)
1433 return false;
1434 /* XXX: hack to remove last recorded key, otherwise upon replay
1435 * we would start another recording */
1436 if (vis->recording->len > 1) {
1437 vis->recording->len--;
1438 vis->recording->data[vis->recording->len-1] = '\0';
1440 vis->last_recording = vis->recording;
1441 vis->recording = NULL;
1442 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1443 return true;
1446 bool vis_macro_recording(Vis *vis) {
1447 return vis->recording;
1450 static void macro_replay(Vis *vis, const Macro *macro) {
1451 const Macro *replaying = vis->replaying;
1452 vis->replaying = macro;
1453 macro_replay_internal(vis, macro);
1454 vis->replaying = replaying;
1457 static void macro_replay_internal(Vis *vis, const Macro *macro) {
1458 size_t pos = buffer_length0(&vis->input_queue);
1459 for (char *key = macro->data, *next; key; key = next) {
1460 char tmp;
1461 next = (char*)vis_keys_next(vis, key);
1462 if (next) {
1463 tmp = *next;
1464 *next = '\0';
1467 vis_keys_push(vis, key, pos, false);
1469 if (next)
1470 *next = tmp;
1474 bool vis_macro_replay(Vis *vis, enum VisRegister id) {
1475 if (id == VIS_REG_SEARCH)
1476 return vis_motion(vis, VIS_MOVE_SEARCH_REPEAT_FORWARD);
1477 if (id == VIS_REG_COMMAND) {
1478 const char *cmd = register_get(vis, &vis->registers[id], NULL);
1479 return vis_cmd(vis, cmd);
1482 Macro *macro = macro_get(vis, id);
1483 if (!macro || macro == vis->recording)
1484 return false;
1485 int count = vis_count_get_default(vis, 1);
1486 vis_cancel(vis);
1487 for (int i = 0; i < count; i++)
1488 macro_replay(vis, macro);
1489 vis_file_snapshot(vis, vis->win->file);
1490 return true;
1493 void vis_repeat(Vis *vis) {
1494 const Macro *macro = vis->action_prev.macro;
1495 int count = vis->action.count;
1496 if (count != VIS_COUNT_UNKNOWN)
1497 vis->action_prev.count = count;
1498 else
1499 count = vis->action_prev.count;
1500 vis->action = vis->action_prev;
1501 vis_do(vis);
1502 if (macro) {
1503 Mode *mode = vis->mode;
1504 Action action_prev = vis->action_prev;
1505 if (count < 1 ||
1506 action_prev.op == &vis_operators[VIS_OP_CHANGE] ||
1507 action_prev.op == &vis_operators[VIS_OP_FILTER])
1508 count = 1;
1509 if (vis->action_prev.op == &vis_operators[VIS_OP_MODESWITCH])
1510 vis->action_prev.count = 1;
1511 for (int i = 0; i < count; i++) {
1512 if (vis->interrupted)
1513 break;
1514 mode_set(vis, mode);
1515 macro_replay(vis, macro);
1517 vis->action_prev = action_prev;
1519 vis_cancel(vis);
1520 vis_file_snapshot(vis, vis->win->file);
1523 enum VisMark vis_mark_from(Vis *vis, char mark) {
1524 if (mark >= 'a' && mark <= 'z')
1525 return VIS_MARK_a + mark - 'a';
1526 for (size_t i = 0; i < LENGTH(vis_marks); i++) {
1527 if (vis_marks[i].name == mark)
1528 return i;
1530 return VIS_MARK_INVALID;
1533 void vis_mark_set(Vis *vis, enum VisMark mark, size_t pos) {
1534 File *file = vis->win->file;
1535 if (mark < LENGTH(file->marks))
1536 file->marks[mark] = text_mark_set(file->text, pos);
1539 int vis_count_get(Vis *vis) {
1540 return vis->action.count;
1543 int vis_count_get_default(Vis *vis, int def) {
1544 if (vis->action.count == VIS_COUNT_UNKNOWN)
1545 return def;
1546 return vis->action.count;
1549 void vis_count_set(Vis *vis, int count) {
1550 vis->action.count = (count >= 0 ? count : VIS_COUNT_UNKNOWN);
1553 VisCountIterator vis_count_iterator_get(Vis *vis, int def) {
1554 return (VisCountIterator) {
1555 .vis = vis,
1556 .iteration = 0,
1557 .count = vis_count_get_default(vis, def),
1561 VisCountIterator vis_count_iterator_init(Vis *vis, int count) {
1562 return (VisCountIterator) {
1563 .vis = vis,
1564 .iteration = 0,
1565 .count = count,
1569 bool vis_count_iterator_next(VisCountIterator *it) {
1570 if (it->vis->interrupted)
1571 return false;
1572 return it->iteration++ < it->count;
1575 void vis_exit(Vis *vis, int status) {
1576 vis->running = false;
1577 vis->exit_status = status;
1580 void vis_insert_tab(Vis *vis) {
1581 if (!vis->expandtab) {
1582 vis_insert_key(vis, "\t", 1);
1583 return;
1585 char spaces[9];
1586 int tabwidth = MIN(vis->tabwidth, LENGTH(spaces) - 1);
1587 for (Selection *s = view_selections(vis->win->view); s; s = view_selections_next(s)) {
1588 size_t pos = view_cursors_pos(s);
1589 int width = text_line_width_get(vis->win->file->text, pos);
1590 int count = tabwidth - (width % tabwidth);
1591 for (int i = 0; i < count; i++)
1592 spaces[i] = ' ';
1593 spaces[count] = '\0';
1594 vis_insert(vis, pos, spaces, count);
1595 view_cursors_scroll_to(s, pos + count);
1599 size_t vis_text_insert_nl(Vis *vis, Text *txt, size_t pos) {
1600 size_t indent_len = 0;
1601 char byte, *indent = NULL;
1602 /* insert second newline at end of file, except if there is already one */
1603 bool eof = pos == text_size(txt);
1604 bool nl2 = eof && !(pos > 0 && text_byte_get(txt, pos-1, &byte) && byte == '\n');
1606 if (vis->autoindent) {
1607 /* copy leading white space of current line */
1608 size_t begin = text_line_begin(txt, pos);
1609 size_t start = text_line_start(txt, begin);
1610 size_t end = text_line_end(txt, start);
1611 if (start > pos)
1612 start = pos;
1613 indent_len = start >= begin ? start-begin : 0;
1614 if (start == end) {
1615 pos = begin;
1616 } else {
1617 indent = malloc(indent_len+1);
1618 if (indent)
1619 indent_len = text_bytes_get(txt, begin, indent_len, indent);
1623 text_insert(txt, pos, "\n", 1);
1624 if (eof) {
1625 if (nl2)
1626 text_insert(txt, text_size(txt), "\n", 1);
1627 else
1628 pos--; /* place cursor before, not after nl */
1630 pos++;
1632 if (indent)
1633 text_insert(txt, pos, indent, indent_len);
1634 free(indent);
1635 return pos + indent_len;
1638 void vis_insert_nl(Vis *vis) {
1639 Win *win = vis->win;
1640 View *view = win->view;
1641 Text *txt = win->file->text;
1642 for (Selection *s = view_selections(view); s; s = view_selections_next(s)) {
1643 size_t pos = view_cursors_pos(s);
1644 size_t newpos = vis_text_insert_nl(vis, txt, pos);
1645 /* This is a bit of a hack to fix cursor positioning when
1646 * inserting a new line at the start of the view port.
1647 * It has the effect of reseting the mark used by the view
1648 * code to keep track of the start of the visible region.
1650 view_cursors_to(s, pos);
1651 view_cursors_to(s, newpos);
1653 vis_window_invalidate(win);
1656 Regex *vis_regex(Vis *vis, const char *pattern) {
1657 if (!pattern && !(pattern = register_get(vis, &vis->registers[VIS_REG_SEARCH], NULL)))
1658 return NULL;
1659 Regex *regex = text_regex_new();
1660 if (!regex)
1661 return NULL;
1662 if (text_regex_compile(regex, pattern, REG_EXTENDED|REG_NEWLINE) != 0) {
1663 text_regex_free(regex);
1664 return NULL;
1666 register_put0(vis, &vis->registers[VIS_REG_SEARCH], pattern);
1667 return regex;
1670 int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
1671 void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
1672 void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len)) {
1674 /* if an invalid range was given, stdin (i.e. key board input) is passed
1675 * through the external command. */
1676 Text *text = file->text;
1677 int pin[2], pout[2], perr[2], status = -1;
1678 bool interactive = !text_range_valid(range);
1679 Filerange rout = interactive ? text_range_new(0, 0) : *range;
1681 if (pipe(pin) == -1)
1682 return -1;
1683 if (pipe(pout) == -1) {
1684 close(pin[0]);
1685 close(pin[1]);
1686 return -1;
1689 if (pipe(perr) == -1) {
1690 close(pin[0]);
1691 close(pin[1]);
1692 close(pout[0]);
1693 close(pout[1]);
1694 return -1;
1697 vis->ui->terminal_save(vis->ui);
1698 pid_t pid = fork();
1700 if (pid == -1) {
1701 close(pin[0]);
1702 close(pin[1]);
1703 close(pout[0]);
1704 close(pout[1]);
1705 close(perr[0]);
1706 close(perr[1]);
1707 vis_info_show(vis, "fork failure: %s", strerror(errno));
1708 return -1;
1709 } else if (pid == 0) { /* child i.e filter */
1710 sigset_t sigterm_mask;
1711 sigemptyset(&sigterm_mask);
1712 sigaddset(&sigterm_mask, SIGTERM);
1713 if (sigprocmask(SIG_UNBLOCK, &sigterm_mask, NULL) == -1) {
1714 fprintf(stderr, "failed to reset signal mask");
1715 exit(EXIT_FAILURE);
1718 int null = open("/dev/null", O_RDWR);
1719 if (null == -1) {
1720 fprintf(stderr, "failed to open /dev/null");
1721 exit(EXIT_FAILURE);
1724 if (!interactive) {
1725 /* If we have nothing to write, let stdin point to
1726 * /dev/null instead of a pipe which is immediately
1727 * closed. Some programs behave differently when used
1728 * in a pipeline.
1730 if (text_range_size(range) == 0)
1731 dup2(null, STDIN_FILENO);
1732 else
1733 dup2(pin[0], STDIN_FILENO);
1736 close(pin[0]);
1737 close(pin[1]);
1738 if (interactive) {
1739 dup2(STDERR_FILENO, STDOUT_FILENO);
1740 /* For some reason the first byte written by the
1741 * interactive application is not being displayed.
1742 * It probably has something to do with the terminal
1743 * state change. By writing a dummy byte ourself we
1744 * ensure that the complete output is visible.
1746 while(write(STDOUT_FILENO, " ", 1) == -1 && errno == EINTR);
1747 } else if (read_stdout) {
1748 dup2(pout[1], STDOUT_FILENO);
1749 } else {
1750 dup2(null, STDOUT_FILENO);
1752 close(pout[1]);
1753 close(pout[0]);
1754 if (!interactive) {
1755 if (read_stderr)
1756 dup2(perr[1], STDERR_FILENO);
1757 else
1758 dup2(null, STDERR_FILENO);
1760 close(perr[0]);
1761 close(perr[1]);
1762 close(null);
1764 if (file->name) {
1765 char *name = strrchr(file->name, '/');
1766 setenv("vis_filepath", file->name, 1);
1767 setenv("vis_filename", name ? name+1 : file->name, 1);
1770 if (!argv[1])
1771 execlp(vis->shell, vis->shell, "-c", argv[0], (char*)NULL);
1772 else
1773 execvp(argv[0], (char* const*)argv);
1774 fprintf(stderr, "exec failure: %s", strerror(errno));
1775 exit(EXIT_FAILURE);
1778 vis->interrupted = false;
1780 close(pin[0]);
1781 close(pout[1]);
1782 close(perr[1]);
1784 if (fcntl(pout[0], F_SETFL, O_NONBLOCK) == -1 ||
1785 fcntl(perr[0], F_SETFL, O_NONBLOCK) == -1)
1786 goto err;
1788 fd_set rfds, wfds;
1790 do {
1791 if (vis->interrupted) {
1792 kill(0, SIGTERM);
1793 break;
1796 FD_ZERO(&rfds);
1797 FD_ZERO(&wfds);
1798 if (pin[1] != -1)
1799 FD_SET(pin[1], &wfds);
1800 if (pout[0] != -1)
1801 FD_SET(pout[0], &rfds);
1802 if (perr[0] != -1)
1803 FD_SET(perr[0], &rfds);
1805 if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) {
1806 if (errno == EINTR)
1807 continue;
1808 vis_info_show(vis, "Select failure");
1809 break;
1812 if (pin[1] != -1 && FD_ISSET(pin[1], &wfds)) {
1813 Filerange junk = rout;
1814 if (junk.end > junk.start + PIPE_BUF)
1815 junk.end = junk.start + PIPE_BUF;
1816 ssize_t len = text_write_range(text, &junk, pin[1]);
1817 if (len > 0) {
1818 rout.start += len;
1819 if (text_range_size(&rout) == 0) {
1820 close(pout[1]);
1821 pout[1] = -1;
1823 } else {
1824 close(pin[1]);
1825 pin[1] = -1;
1826 if (len == -1)
1827 vis_info_show(vis, "Error writing to external command");
1831 if (pout[0] != -1 && FD_ISSET(pout[0], &rfds)) {
1832 char buf[BUFSIZ];
1833 ssize_t len = read(pout[0], buf, sizeof buf);
1834 if (len > 0) {
1835 if (read_stdout)
1836 (*read_stdout)(stdout_context, buf, len);
1837 } else if (len == 0) {
1838 close(pout[0]);
1839 pout[0] = -1;
1840 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1841 vis_info_show(vis, "Error reading from filter stdout");
1842 close(pout[0]);
1843 pout[0] = -1;
1847 if (perr[0] != -1 && FD_ISSET(perr[0], &rfds)) {
1848 char buf[BUFSIZ];
1849 ssize_t len = read(perr[0], buf, sizeof buf);
1850 if (len > 0) {
1851 if (read_stderr)
1852 (*read_stderr)(stderr_context, buf, len);
1853 } else if (len == 0) {
1854 close(perr[0]);
1855 perr[0] = -1;
1856 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1857 vis_info_show(vis, "Error reading from filter stderr");
1858 close(perr[0]);
1859 perr[0] = -1;
1863 } while (pin[1] != -1 || pout[0] != -1 || perr[0] != -1);
1865 err:
1866 if (pin[1] != -1)
1867 close(pin[1]);
1868 if (pout[0] != -1)
1869 close(pout[0]);
1870 if (perr[0] != -1)
1871 close(perr[0]);
1873 for (;;) {
1874 if (vis->interrupted)
1875 kill(0, SIGTERM);
1876 pid_t died = waitpid(pid, &status, 0);
1877 if ((died == -1 && errno == ECHILD) || pid == died)
1878 break;
1881 /* clear any pending SIGTERM */
1882 struct sigaction sigterm_ignore, sigterm_old;
1883 sigterm_ignore.sa_handler = SIG_IGN;
1884 sigterm_ignore.sa_flags = 0;
1885 sigemptyset(&sigterm_ignore.sa_mask);
1887 sigaction(SIGTERM, &sigterm_ignore, &sigterm_old);
1888 sigaction(SIGTERM, &sigterm_old, NULL);
1890 vis->interrupted = false;
1891 vis->ui->terminal_restore(vis->ui);
1893 return status;
1896 static ssize_t read_buffer(void *context, char *data, size_t len) {
1897 buffer_append(context, data, len);
1898 return len;
1901 int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[], char **out, char **err) {
1902 Buffer bufout, buferr;
1903 buffer_init(&bufout);
1904 buffer_init(&buferr);
1905 int status = vis_pipe(vis, file, range, argv,
1906 &bufout, out ? read_buffer : NULL,
1907 &buferr, err ? read_buffer : NULL);
1908 buffer_terminate(&bufout);
1909 buffer_terminate(&buferr);
1910 if (out)
1911 *out = buffer_move(&bufout);
1912 if (err)
1913 *err = buffer_move(&buferr);
1914 buffer_release(&bufout);
1915 buffer_release(&buferr);
1916 return status;
1919 bool vis_cmd(Vis *vis, const char *cmdline) {
1920 if (!cmdline)
1921 return true;
1922 while (*cmdline == ':')
1923 cmdline++;
1924 size_t len = strlen(cmdline);
1925 char *line = malloc(len+2);
1926 if (!line)
1927 return false;
1928 strncpy(line, cmdline, len+1);
1930 for (char *end = line + len - 1; end >= line && isspace((unsigned char)*end); end--)
1931 *end = '\0';
1933 enum SamError err = sam_cmd(vis, line);
1934 if (err != SAM_ERR_OK)
1935 vis_info_show(vis, "%s", sam_error(err));
1936 free(line);
1937 return err == SAM_ERR_OK;
1940 void vis_file_snapshot(Vis *vis, File *file) {
1941 if (!vis->replaying)
1942 text_snapshot(file->text);
1945 Text *vis_text(Vis *vis) {
1946 return vis->win->file->text;
1949 View *vis_view(Vis *vis) {
1950 return vis->win->view;
1953 Win *vis_window(Vis *vis) {
1954 return vis->win;
1957 bool vis_get_autoindent(const Vis *vis) {
1958 return vis->autoindent;