Add gemini lexer
[vis.git] / vis.c
blobd39c4b19fe0f57e87f1ebd1cfd1ac6d375a30ee0
1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <signal.h>
6 #include <stdarg.h>
7 #include <stdio.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <limits.h>
11 #include <ctype.h>
12 #include <time.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <sys/stat.h>
17 #include <sys/ioctl.h>
18 #include <sys/mman.h>
19 #include <pwd.h>
20 #include <libgen.h>
21 #include <termkey.h>
23 #include "vis.h"
24 #include "text-util.h"
25 #include "text-motions.h"
26 #include "text-objects.h"
27 #include "util.h"
28 #include "vis-core.h"
29 #include "sam.h"
30 #include "ui.h"
33 static void macro_replay(Vis *vis, const Macro *macro);
34 static void macro_replay_internal(Vis *vis, const Macro *macro);
35 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record);
37 bool vis_event_emit(Vis *vis, enum VisEvents id, ...) {
38 if (!vis->event)
39 return true;
41 if (!vis->initialized) {
42 vis->initialized = true;
43 vis->ui->init(vis->ui, vis);
44 if (vis->event->init)
45 vis->event->init(vis);
48 va_list ap;
49 va_start(ap, id);
50 bool ret = true;
52 switch (id) {
53 case VIS_EVENT_INIT:
54 break;
55 case VIS_EVENT_START:
56 if (vis->event->start)
57 vis->event->start(vis);
58 break;
59 case VIS_EVENT_FILE_OPEN:
60 case VIS_EVENT_FILE_SAVE_PRE:
61 case VIS_EVENT_FILE_SAVE_POST:
62 case VIS_EVENT_FILE_CLOSE:
64 File *file = va_arg(ap, File*);
65 if (file->internal)
66 break;
67 if (id == VIS_EVENT_FILE_OPEN && vis->event->file_open) {
68 vis->event->file_open(vis, file);
69 } else if (id == VIS_EVENT_FILE_SAVE_PRE && vis->event->file_save_pre) {
70 const char *path = va_arg(ap, const char*);
71 ret = vis->event->file_save_pre(vis, file, path);
72 } else if (id == VIS_EVENT_FILE_SAVE_POST && vis->event->file_save_post) {
73 const char *path = va_arg(ap, const char*);
74 vis->event->file_save_post(vis, file, path);
75 } else if (id == VIS_EVENT_FILE_CLOSE && vis->event->file_close) {
76 vis->event->file_close(vis, file);
78 break;
80 case VIS_EVENT_WIN_OPEN:
81 case VIS_EVENT_WIN_CLOSE:
82 case VIS_EVENT_WIN_HIGHLIGHT:
83 case VIS_EVENT_WIN_STATUS:
85 Win *win = va_arg(ap, Win*);
86 if (win->file->internal && id != VIS_EVENT_WIN_STATUS)
87 break;
88 if (vis->event->win_open && id == VIS_EVENT_WIN_OPEN) {
89 vis->event->win_open(vis, win);
90 } else if (vis->event->win_close && id == VIS_EVENT_WIN_CLOSE) {
91 vis->event->win_close(vis, win);
92 } else if (vis->event->win_highlight && id == VIS_EVENT_WIN_HIGHLIGHT) {
93 vis->event->win_highlight(vis, win);
94 } else if (vis->event->win_status && id == VIS_EVENT_WIN_STATUS) {
95 vis->event->win_status(vis, win);
97 break;
99 case VIS_EVENT_QUIT:
100 if (vis->event->quit)
101 vis->event->quit(vis);
102 break;
103 case VIS_EVENT_TERM_CSI:
104 if (vis->event->term_csi)
105 vis->event->term_csi(vis, va_arg(ap, const long *));
106 break;
109 va_end(ap);
110 return ret;
113 /** window / file handling */
115 static void file_free(Vis *vis, File *file) {
116 if (!file)
117 return;
118 if (file->refcount > 1) {
119 --file->refcount;
120 return;
122 vis_event_emit(vis, VIS_EVENT_FILE_CLOSE, file);
123 for (size_t i = 0; i < LENGTH(file->marks); i++)
124 mark_release(&file->marks[i]);
125 text_free(file->text);
126 free((char*)file->name);
128 if (file->prev)
129 file->prev->next = file->next;
130 if (file->next)
131 file->next->prev = file->prev;
132 if (vis->files == file)
133 vis->files = file->next;
134 free(file);
137 static File *file_new_text(Vis *vis, Text *text) {
138 File *file = calloc(1, sizeof(*file));
139 if (!file)
140 return NULL;
141 file->fd = -1;
142 file->text = text;
143 file->stat = text_stat(text);
144 for (size_t i = 0; i < LENGTH(file->marks); i++)
145 mark_init(&file->marks[i]);
146 if (vis->files)
147 vis->files->prev = file;
148 file->next = vis->files;
149 vis->files = file;
150 return file;
153 char *absolute_path(const char *name) {
154 if (!name)
155 return NULL;
156 char *copy1 = strdup(name);
157 char *copy2 = strdup(name);
158 char *path_absolute = NULL;
159 char path_normalized[PATH_MAX] = "";
161 if (!copy1 || !copy2)
162 goto err;
164 char *dir = dirname(copy1);
165 char *base = basename(copy2);
166 if (!(path_absolute = realpath(dir, NULL)))
167 goto err;
168 if (strcmp(path_absolute, "/") == 0)
169 path_absolute[0] = '\0';
171 snprintf(path_normalized, sizeof(path_normalized), "%s/%s",
172 path_absolute, base);
173 err:
174 free(copy1);
175 free(copy2);
176 free(path_absolute);
177 return path_normalized[0] ? strdup(path_normalized) : NULL;
180 static File *file_new(Vis *vis, const char *name) {
181 char *name_absolute = NULL;
182 if (name) {
183 if (!(name_absolute = absolute_path(name)))
184 return NULL;
185 File *existing = NULL;
186 /* try to detect whether the same file is already open in another window
187 * TODO: do this based on inodes */
188 for (File *file = vis->files; file; file = file->next) {
189 if (file->name && strcmp(file->name, name_absolute) == 0) {
190 existing = file;
191 break;
194 if (existing) {
195 free(name_absolute);
196 return existing;
200 File *file = NULL;
201 Text *text = text_load_method(name, vis->load_method);
202 if (!text && name && errno == ENOENT)
203 text = text_load(NULL);
204 if (!text)
205 goto err;
206 if (!(file = file_new_text(vis, text)))
207 goto err;
208 file->name = name_absolute;
209 vis_event_emit(vis, VIS_EVENT_FILE_OPEN, file);
210 return file;
211 err:
212 free(name_absolute);
213 text_free(text);
214 file_free(vis, file);
215 return NULL;
218 static File *file_new_internal(Vis *vis, const char *filename) {
219 File *file = file_new(vis, filename);
220 if (file) {
221 file->refcount = 1;
222 file->internal = true;
224 return file;
227 void file_name_set(File *file, const char *name) {
228 if (name == file->name)
229 return;
230 free((char*)file->name);
231 file->name = absolute_path(name);
234 const char *file_name_get(File *file) {
235 /* TODO: calculate path relative to working directory, cache result */
236 if (!file->name)
237 return NULL;
238 char cwd[PATH_MAX];
239 if (!getcwd(cwd, sizeof cwd))
240 return file->name;
241 const char *path = strstr(file->name, cwd);
242 if (path != file->name)
243 return file->name;
244 size_t cwdlen = strlen(cwd);
245 return file->name[cwdlen] == '/' ? file->name+cwdlen+1 : file->name;
248 void vis_window_status(Win *win, const char *status) {
249 win->ui->status(win->ui, status);
252 void window_selection_save(Win *win) {
253 Vis *vis = win->vis;
254 View *view = win->view;
255 Array sel = view_selections_get_all(view);
256 vis_mark_set(win, VIS_MARK_SELECTION, &sel);
257 array_release(&sel);
258 vis_jumplist_save(vis);
262 static void window_free(Win *win) {
263 if (!win)
264 return;
265 Vis *vis = win->vis;
266 for (Win *other = vis->windows; other; other = other->next) {
267 if (other->parent == win)
268 other->parent = NULL;
270 if (vis->ui)
271 vis->ui->window_free(win->ui);
272 view_free(win->view);
273 for (size_t i = 0; i < LENGTH(win->modes); i++)
274 map_free(win->modes[i].bindings);
275 marklist_release(&win->jumplist);
276 mark_release(&win->saved_selections);
277 free(win);
280 static void window_draw_colorcolumn(Win *win) {
281 View *view = win->view;
282 int cc = view_colorcolumn_get(view);
283 if (cc <= 0)
284 return;
285 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_COLOR_COLUMN);
286 size_t lineno = 0;
287 int line_cols = 0; /* Track the number of columns we've passed on each line */
288 bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */
289 int width = view_width_get(view);
291 for (Line *l = view_lines_first(view); l; l = l->next) {
292 if (l->lineno != lineno) {
293 line_cols = 0;
294 line_cc_set = false;
295 if (!(lineno = l->lineno))
296 break;
298 if (line_cc_set)
299 continue;
301 /* This screen line contains the cell we want to highlight */
302 if (cc <= line_cols + width) {
303 CellStyle *orig = &l->cells[cc - 1 - line_cols].style;
304 orig->attr = style.attr;
305 orig->fg = is_default_color(style.fg) ? orig->fg : style.fg;
306 orig->bg = is_default_color(style.bg) ? orig->bg : style.bg;
307 line_cc_set = true;
308 } else {
309 line_cols += width;
314 static void window_draw_cursorline(Win *win) {
315 Vis *vis = win->vis;
316 View *view = win->view;
317 enum UiOption options = view_options_get(view);
318 if (!(options & UI_OPTION_CURSOR_LINE))
319 return;
320 if (vis->mode->visual || vis->win != win)
321 return;
322 if (view_selections_count(view) > 1)
323 return;
325 int width = view_width_get(view);
326 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_CURSOR_LINE);
327 Selection *sel = view_selections_primary_get(view);
328 size_t lineno = view_cursors_line_get(sel)->lineno;
329 for (Line *l = view_lines_first(view); l; l = l->next) {
330 if (l->lineno == lineno) {
331 for (int x = 0; x < width; x++) {
332 l->cells[x].style.attr |= style.attr;
333 l->cells[x].style.bg = style.bg;
335 } else if (l->lineno > lineno) {
336 break;
341 static void window_draw_selection(View *view, Selection *cur, CellStyle *style) {
342 Filerange sel = view_selections_get(cur);
343 if (!text_range_valid(&sel))
344 return;
345 Line *start_line; int start_col;
346 Line *end_line; int end_col;
347 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
348 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
349 if (!start_line && !end_line)
350 return;
351 if (!start_line) {
352 start_line = view_lines_first(view);
353 start_col = 0;
355 if (!end_line) {
356 end_line = view_lines_last(view);
357 end_col = end_line->width;
359 for (Line *l = start_line; l != end_line->next; l = l->next) {
360 int col = (l == start_line) ? start_col : 0;
361 int end = (l == end_line) ? end_col : l->width;
362 while (col < end) {
363 if (cell_color_equal(l->cells[col].style.fg, style->bg)) {
364 CellStyle old = l->cells[col].style;
365 if (!cell_color_equal(old.fg, old.bg)) {
366 l->cells[col].style.fg = old.bg;
367 l->cells[col].style.bg = old.fg;
368 } else {
369 l->cells[col].style.attr = style->attr;
371 } else {
372 l->cells[col].style.bg = style->bg;
374 col++;
379 static void window_draw_cursor_matching(Win *win, Selection *cur, CellStyle *style) {
380 if (win->vis->mode->visual)
381 return;
382 Line *line_match; int col_match;
383 size_t pos = view_cursors_pos(cur);
384 Filerange limits = view_viewport_get(win->view);
385 size_t pos_match = text_bracket_match_symbol(win->file->text, pos, "(){}[]\"'`", &limits);
386 if (pos == pos_match)
387 return;
388 if (!view_coord_get(win->view, pos_match, &line_match, NULL, &col_match))
389 return;
390 if (cell_color_equal(line_match->cells[col_match].style.fg, style->fg)) {
391 CellStyle old = line_match->cells[col_match].style;
392 line_match->cells[col_match].style.fg = old.bg;
393 line_match->cells[col_match].style.bg = old.fg;
394 } else {
395 line_match->cells[col_match].style.bg = style->bg;
399 static void window_draw_cursor(Win *win, Selection *cur, CellStyle *style, CellStyle *sel_style) {
400 if (win->vis->win != win)
401 return;
402 Line *line = view_cursors_line_get(cur);
403 int col = view_cursors_cell_get(cur);
404 if (!line || col == -1)
405 return;
406 line->cells[col].style = *style;
407 window_draw_cursor_matching(win, cur, sel_style);
408 return;
411 static void window_draw_selections(Win *win) {
412 View *view = win->view;
413 Filerange viewport = view_viewport_get(view);
414 Selection *sel = view_selections_primary_get(view);
415 CellStyle style_cursor = win->ui->style_get(win->ui, UI_STYLE_CURSOR);
416 CellStyle style_cursor_primary = win->ui->style_get(win->ui, UI_STYLE_CURSOR_PRIMARY);
417 CellStyle style_selection = win->ui->style_get(win->ui, UI_STYLE_SELECTION);
418 for (Selection *s = view_selections_prev(sel); s; s = view_selections_prev(s)) {
419 window_draw_selection(win->view, s, &style_selection);
420 size_t pos = view_cursors_pos(s);
421 if (pos < viewport.start)
422 break;
423 window_draw_cursor(win, s, &style_cursor, &style_selection);
425 window_draw_selection(win->view, sel, &style_selection);
426 window_draw_cursor(win, sel, &style_cursor_primary, &style_selection);
427 for (Selection *s = view_selections_next(sel); s; s = view_selections_next(s)) {
428 window_draw_selection(win->view, s, &style_selection);
429 size_t pos = view_cursors_pos(s);
430 if (pos > viewport.end)
431 break;
432 window_draw_cursor(win, s, &style_cursor, &style_selection);
436 static void window_draw_eof(Win *win) {
437 View *view = win->view;
438 if (view_width_get(view) == 0)
439 return;
440 CellStyle style = win->ui->style_get(win->ui, UI_STYLE_EOF);
441 for (Line *l = view_lines_last(view)->next; l; l = l->next) {
442 strncpy(l->cells[0].data, view_symbol_eof_get(view), sizeof(l->cells[0].data)-1);
443 l->cells[0].style = style;
447 void vis_window_draw(Win *win) {
448 if (!win->ui || !view_update(win->view))
449 return;
450 Vis *vis = win->vis;
451 vis_event_emit(vis, VIS_EVENT_WIN_HIGHLIGHT, win);
453 window_draw_colorcolumn(win);
454 window_draw_cursorline(win);
455 if (!vis->win || vis->win == win || vis->win->parent == win)
456 window_draw_selections(win);
457 window_draw_eof(win);
459 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, win);
463 void vis_window_invalidate(Win *win) {
464 for (Win *w = win->vis->windows; w; w = w->next) {
465 if (w->file == win->file)
466 view_draw(w->view);
470 Win *window_new_file(Vis *vis, File *file, enum UiOption options) {
471 Win *win = calloc(1, sizeof(Win));
472 if (!win)
473 return NULL;
474 win->vis = vis;
475 win->file = file;
476 win->view = view_new(file->text);
477 win->ui = vis->ui->window_new(vis->ui, win, options);
478 if (!win->view || !win->ui) {
479 window_free(win);
480 return NULL;
482 marklist_init(&win->jumplist, 32);
483 mark_init(&win->saved_selections);
484 file->refcount++;
485 view_options_set(win->view, view_options_get(win->view));
486 view_tabwidth_set(win->view, vis->tabwidth);
488 if (vis->windows)
489 vis->windows->prev = win;
490 win->next = vis->windows;
491 vis->windows = win;
492 vis->win = win;
493 vis->ui->window_focus(win->ui);
494 for (size_t i = 0; i < LENGTH(win->modes); i++)
495 win->modes[i].parent = &vis_modes[i];
496 vis_event_emit(vis, VIS_EVENT_WIN_OPEN, win);
497 return win;
500 bool vis_window_reload(Win *win) {
501 const char *name = win->file->name;
502 if (!name)
503 return false; /* can't reload unsaved file */
504 /* temporarily unset file name, otherwise file_new returns the same File */
505 win->file->name = NULL;
506 File *file = file_new(win->vis, name);
507 win->file->name = name;
508 if (!file)
509 return false;
510 file_free(win->vis, win->file);
511 file->refcount = 1;
512 win->file = file;
513 view_reload(win->view, file->text);
514 return true;
517 bool vis_window_split(Win *original) {
518 Win *win = window_new_file(original->vis, original->file, UI_OPTION_STATUSBAR);
519 if (!win)
520 return false;
521 for (size_t i = 0; i < LENGTH(win->modes); i++) {
522 if (original->modes[i].bindings)
523 win->modes[i].bindings = map_new();
524 if (win->modes[i].bindings)
525 map_copy(win->modes[i].bindings, original->modes[i].bindings);
527 win->file = original->file;
528 view_options_set(win->view, view_options_get(original->view));
529 view_cursor_to(win->view, view_cursor_get(original->view));
530 return true;
533 void vis_window_focus(Win *win) {
534 if (!win)
535 return;
536 Vis *vis = win->vis;
537 vis->win = win;
538 vis->ui->window_focus(win->ui);
541 void vis_window_next(Vis *vis) {
542 Win *sel = vis->win;
543 if (!sel)
544 return;
545 vis_window_focus(sel->next ? sel->next : vis->windows);
548 void vis_window_prev(Vis *vis) {
549 Win *sel = vis->win;
550 if (!sel)
551 return;
552 sel = sel->prev;
553 if (!sel)
554 for (sel = vis->windows; sel->next; sel = sel->next);
555 vis_window_focus(sel);
558 int vis_window_width_get(const Win *win) {
559 return win->ui->window_width(win->ui);
562 int vis_window_height_get(const Win *win) {
563 return win->ui->window_height(win->ui);
566 void vis_draw(Vis *vis) {
567 for (Win *win = vis->windows; win; win = win->next)
568 view_draw(win->view);
571 void vis_redraw(Vis *vis) {
572 vis->ui->redraw(vis->ui);
573 vis_update(vis);
576 void vis_update(Vis *vis) {
577 vis->ui->draw(vis->ui);
580 void vis_suspend(Vis *vis) {
581 vis->ui->suspend(vis->ui);
584 void vis_resume(Vis *vis) {
585 vis->ui->resume(vis->ui);
588 bool vis_window_new(Vis *vis, const char *filename) {
589 File *file = file_new(vis, filename);
590 if (!file)
591 return false;
592 Win *win = window_new_file(vis, file, UI_OPTION_STATUSBAR|UI_OPTION_SYMBOL_EOF);
593 if (!win) {
594 file_free(vis, file);
595 return false;
598 return true;
601 bool vis_window_new_fd(Vis *vis, int fd) {
602 if (fd == -1)
603 return false;
604 if (!vis_window_new(vis, NULL))
605 return false;
606 vis->win->file->fd = fd;
607 return true;
610 bool vis_window_closable(Win *win) {
611 if (!win || !text_modified(win->file->text))
612 return true;
613 return win->file->refcount > 1;
616 void vis_window_swap(Win *a, Win *b) {
617 if (a == b || !a || !b)
618 return;
619 Vis *vis = a->vis;
620 Win *tmp = a->next;
621 a->next = b->next;
622 b->next = tmp;
623 if (a->next)
624 a->next->prev = a;
625 if (b->next)
626 b->next->prev = b;
627 tmp = a->prev;
628 a->prev = b->prev;
629 b->prev = tmp;
630 if (a->prev)
631 a->prev->next = a;
632 if (b->prev)
633 b->prev->next = b;
634 if (vis->windows == a)
635 vis->windows = b;
636 else if (vis->windows == b)
637 vis->windows = a;
638 vis->ui->window_swap(a->ui, b->ui);
639 if (vis->win == a)
640 vis_window_focus(b);
641 else if (vis->win == b)
642 vis_window_focus(a);
645 void vis_window_close(Win *win) {
646 if (!win)
647 return;
648 Vis *vis = win->vis;
649 vis_event_emit(vis, VIS_EVENT_WIN_CLOSE, win);
650 file_free(vis, win->file);
651 if (win->prev)
652 win->prev->next = win->next;
653 if (win->next)
654 win->next->prev = win->prev;
655 if (vis->windows == win)
656 vis->windows = win->next;
657 if (vis->win == win)
658 vis->win = win->next ? win->next : win->prev;
659 if (win == vis->message_window)
660 vis->message_window = NULL;
661 window_free(win);
662 if (vis->win)
663 vis->ui->window_focus(vis->win->ui);
664 vis_draw(vis);
667 Vis *vis_new(Ui *ui, VisEvent *event) {
668 if (!ui)
669 return NULL;
670 Vis *vis = calloc(1, sizeof(Vis));
671 if (!vis)
672 return NULL;
673 vis->exit_status = -1;
674 vis->ui = ui;
675 vis->tabwidth = 8;
676 vis->expandtab = false;
677 vis->change_colors = true;
678 for (size_t i = 0; i < LENGTH(vis->registers); i++)
679 register_init(&vis->registers[i]);
680 vis->registers[VIS_REG_BLACKHOLE].type = REGISTER_BLACKHOLE;
681 vis->registers[VIS_REG_CLIPBOARD].type = REGISTER_CLIPBOARD;
682 vis->registers[VIS_REG_PRIMARY].type = REGISTER_CLIPBOARD;
683 vis->registers[VIS_REG_NUMBER].type = REGISTER_NUMBER;
684 array_init(&vis->operators);
685 array_init(&vis->motions);
686 array_init(&vis->textobjects);
687 array_init(&vis->bindings);
688 array_init(&vis->actions_user);
689 action_reset(&vis->action);
690 buffer_init(&vis->input_queue);
691 if (!(vis->command_file = file_new_internal(vis, NULL)))
692 goto err;
693 if (!(vis->search_file = file_new_internal(vis, NULL)))
694 goto err;
695 if (!(vis->error_file = file_new_internal(vis, NULL)))
696 goto err;
697 if (!(vis->actions = map_new()))
698 goto err;
699 if (!(vis->keymap = map_new()))
700 goto err;
701 if (!sam_init(vis))
702 goto err;
703 struct passwd *pw;
704 char *shell = getenv("SHELL");
705 if ((!shell || !*shell) && (pw = getpwuid(getuid())))
706 shell = pw->pw_shell;
707 if (!shell || !*shell)
708 shell = "/bin/sh";
709 if (!(vis->shell = strdup(shell)))
710 goto err;
711 vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL];
712 vis->event = event;
713 if (event) {
714 if (event->mode_insert_input)
715 vis_modes[VIS_MODE_INSERT].input = event->mode_insert_input;
716 if (event->mode_replace_input)
717 vis_modes[VIS_MODE_REPLACE].input = event->mode_replace_input;
719 return vis;
720 err:
721 vis_free(vis);
722 return NULL;
725 void vis_free(Vis *vis) {
726 if (!vis)
727 return;
728 vis_event_emit(vis, VIS_EVENT_QUIT);
729 vis->event = NULL;
730 while (vis->windows)
731 vis_window_close(vis->windows);
732 file_free(vis, vis->command_file);
733 file_free(vis, vis->search_file);
734 file_free(vis, vis->error_file);
735 for (int i = 0; i < LENGTH(vis->registers); i++)
736 register_release(&vis->registers[i]);
737 vis->ui->free(vis->ui);
738 if (vis->usercmds) {
739 const char *name;
740 while (map_first(vis->usercmds, &name) && vis_cmd_unregister(vis, name));
742 map_free(vis->usercmds);
743 map_free(vis->cmds);
744 if (vis->options) {
745 const char *name;
746 while (map_first(vis->options, &name) && vis_option_unregister(vis, name));
748 map_free(vis->options);
749 map_free(vis->actions);
750 map_free(vis->keymap);
751 buffer_release(&vis->input_queue);
752 for (int i = 0; i < VIS_MODE_INVALID; i++)
753 map_free(vis_modes[i].bindings);
754 array_release_full(&vis->operators);
755 array_release_full(&vis->motions);
756 array_release_full(&vis->textobjects);
757 while (array_length(&vis->bindings))
758 vis_binding_free(vis, array_get_ptr(&vis->bindings, 0));
759 array_release(&vis->bindings);
760 while (array_length(&vis->actions_user))
761 vis_action_free(vis, array_get_ptr(&vis->actions_user, 0));
762 array_release(&vis->actions_user);
763 free(vis->shell);
764 free(vis);
767 void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) {
768 Win *win = vis->win;
769 if (!win)
770 return;
771 text_insert(win->file->text, pos, data, len);
772 vis_window_invalidate(win);
775 void vis_insert_key(Vis *vis, const char *data, size_t len) {
776 Win *win = vis->win;
777 if (!win)
778 return;
779 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s)) {
780 size_t pos = view_cursors_pos(s);
781 vis_insert(vis, pos, data, len);
782 view_cursors_scroll_to(s, pos + len);
786 void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) {
787 Win *win = vis->win;
788 if (!win)
789 return;
790 Text *txt = win->file->text;
791 Iterator it = text_iterator_get(txt, pos);
792 int chars = text_char_count(data, len);
793 for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\n'; )
794 text_iterator_char_next(&it, NULL);
796 text_delete(txt, pos, it.pos - pos);
797 vis_insert(vis, pos, data, len);
800 void vis_replace_key(Vis *vis, const char *data, size_t len) {
801 Win *win = vis->win;
802 if (!win)
803 return;
804 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s)) {
805 size_t pos = view_cursors_pos(s);
806 vis_replace(vis, pos, data, len);
807 view_cursors_scroll_to(s, pos + len);
811 void vis_delete(Vis *vis, size_t pos, size_t len) {
812 Win *win = vis->win;
813 if (!win)
814 return;
815 text_delete(win->file->text, pos, len);
816 vis_window_invalidate(win);
819 bool vis_action_register(Vis *vis, const KeyAction *action) {
820 return map_put(vis->actions, action->name, action);
823 bool vis_keymap_add(Vis *vis, const char *key, const char *mapping) {
824 return map_put(vis->keymap, key, mapping);
827 void vis_keymap_disable(Vis *vis) {
828 vis->keymap_disabled = true;
831 void vis_interrupt(Vis *vis) {
832 vis->interrupted = true;
835 bool vis_interrupt_requested(Vis *vis) {
836 return vis->interrupted;
839 void vis_do(Vis *vis) {
840 Win *win = vis->win;
841 if (!win)
842 return;
843 File *file = win->file;
844 Text *txt = file->text;
845 View *view = win->view;
846 Action *a = &vis->action;
848 int count = MAX(a->count, 1);
849 if (a->op == &vis_operators[VIS_OP_MODESWITCH])
850 count = 1; /* count should apply to inserted text not motion */
851 bool repeatable = a->op && !vis->macro_operator && !vis->win->parent;
852 bool multiple_cursors = view_selections_count(view) > 1;
854 bool linewise = !(a->type & CHARWISE) && (
855 a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) ||
856 vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]);
858 Register *reg = a->reg;
859 size_t reg_slot = multiple_cursors ? EPOS : 0;
860 size_t last_reg_slot = reg_slot;
861 if (!reg)
862 reg = &vis->registers[file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT];
863 if (a->op == &vis_operators[VIS_OP_PUT_AFTER] && multiple_cursors && vis_register_count(vis, reg) == 1)
864 reg_slot = 0;
866 if (vis->mode->visual && a->op)
867 window_selection_save(win);
869 for (Selection *sel = view_selections(view), *next; sel; sel = next) {
870 if (vis->interrupted)
871 break;
873 next = view_selections_next(sel);
875 size_t pos = view_cursors_pos(sel);
876 if (pos == EPOS) {
877 if (!view_selections_dispose(sel))
878 view_cursors_to(sel, 0);
879 continue;
882 OperatorContext c = {
883 .count = count,
884 .pos = pos,
885 .newpos = EPOS,
886 .range = text_range_empty(),
887 .reg = reg,
888 .reg_slot = reg_slot == EPOS ? (size_t)view_selections_number(sel) : reg_slot,
889 .linewise = linewise,
890 .arg = &a->arg,
891 .context = a->op ? a->op->context : NULL,
894 last_reg_slot = c.reg_slot;
896 bool err = false;
897 if (a->movement) {
898 size_t start = pos;
899 for (int i = 0; i < count; i++) {
900 size_t pos_prev = pos;
901 if (a->movement->txt)
902 pos = a->movement->txt(txt, pos);
903 else if (a->movement->cur)
904 pos = a->movement->cur(sel);
905 else if (a->movement->file)
906 pos = a->movement->file(vis, file, sel);
907 else if (a->movement->vis)
908 pos = a->movement->vis(vis, txt, pos);
909 else if (a->movement->view)
910 pos = a->movement->view(vis, view);
911 else if (a->movement->win)
912 pos = a->movement->win(vis, win, pos);
913 else if (a->movement->user)
914 pos = a->movement->user(vis, win, a->movement->data, pos);
915 if (pos == EPOS || a->movement->type & IDEMPOTENT || pos == pos_prev) {
916 err = a->movement->type & COUNT_EXACT;
917 break;
921 if (err) {
922 repeatable = false;
923 continue; // break?
926 if (pos == EPOS) {
927 c.range.start = start;
928 c.range.end = start;
929 pos = start;
930 } else {
931 c.range = text_range_new(start, pos);
932 c.newpos = pos;
935 if (!a->op) {
936 if (a->movement->type & CHARWISE)
937 view_cursors_scroll_to(sel, pos);
938 else
939 view_cursors_to(sel, pos);
940 if (vis->mode->visual)
941 c.range = view_selections_get(sel);
942 } else if (a->movement->type & INCLUSIVE && c.range.end > start) {
943 c.range.end = text_char_next(txt, c.range.end);
944 } else if (linewise && (a->movement->type & LINEWISE_INCLUSIVE)) {
945 c.range.end = text_char_next(txt, c.range.end);
947 } else if (a->textobj) {
948 if (vis->mode->visual)
949 c.range = view_selections_get(sel);
950 else
951 c.range.start = c.range.end = pos;
952 for (int i = 0; i < count; i++) {
953 Filerange r = text_range_empty();
954 if (a->textobj->txt)
955 r = a->textobj->txt(txt, pos);
956 else if (a->textobj->vis)
957 r = a->textobj->vis(vis, txt, pos);
958 else if (a->textobj->user)
959 r = a->textobj->user(vis, win, a->textobj->data, pos);
960 if (!text_range_valid(&r))
961 break;
962 if (a->textobj->type & TEXTOBJECT_DELIMITED_OUTER) {
963 r.start--;
964 r.end++;
967 if (vis->mode->visual || (i > 0 && !(a->textobj->type & TEXTOBJECT_NON_CONTIGUOUS)))
968 c.range = text_range_union(&c.range, &r);
969 else
970 c.range = r;
972 if (i < count - 1) {
973 if (a->textobj->type & TEXTOBJECT_EXTEND_BACKWARD) {
974 pos = c.range.start;
975 if ((a->textobj->type & TEXTOBJECT_DELIMITED_INNER) && pos > 0)
976 pos--;
977 } else {
978 pos = c.range.end;
979 if (a->textobj->type & TEXTOBJECT_DELIMITED_INNER)
980 pos++;
984 } else if (vis->mode->visual) {
985 c.range = view_selections_get(sel);
986 if (!text_range_valid(&c.range))
987 c.range.start = c.range.end = pos;
990 if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL])
991 c.range = text_range_linewise(txt, &c.range);
992 if (vis->mode->visual) {
993 view_selections_set(sel, &c.range);
994 view_selections_anchor(sel, true);
997 if (a->op) {
998 size_t pos = a->op->func(vis, txt, &c);
999 if (pos == EPOS) {
1000 view_selections_dispose(sel);
1001 } else if (pos <= text_size(txt)) {
1002 view_selection_clear(sel);
1003 view_cursors_to(sel, pos);
1008 view_selections_normalize(view);
1009 if (a->movement && (a->movement->type & JUMP))
1010 vis_jumplist_save(vis);
1012 if (a->op) {
1014 if (a->op == &vis_operators[VIS_OP_YANK] ||
1015 a->op == &vis_operators[VIS_OP_DELETE] ||
1016 a->op == &vis_operators[VIS_OP_CHANGE] ||
1017 a->op == &vis_operators[VIS_OP_REPLACE]) {
1018 register_resize(reg, last_reg_slot+1);
1021 /* we do not support visual repeat, still do something resonable */
1022 if (vis->mode->visual && !a->movement && !a->textobj)
1023 a->movement = &vis_motions[VIS_MOVE_NOP];
1025 /* operator implementations must not change the mode,
1026 * they might get called multiple times (once for every cursor)
1028 if (a->op == &vis_operators[VIS_OP_CHANGE]) {
1029 vis_mode_switch(vis, VIS_MODE_INSERT);
1030 } else if (a->op == &vis_operators[VIS_OP_MODESWITCH]) {
1031 vis_mode_switch(vis, a->mode);
1032 } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) {
1033 mode_set(vis, vis->mode_prev);
1034 } else if (vis->mode->visual) {
1035 vis_mode_switch(vis, VIS_MODE_NORMAL);
1038 if (vis->mode == &vis_modes[VIS_MODE_NORMAL])
1039 vis_file_snapshot(vis, file);
1040 vis_draw(vis);
1043 if (a != &vis->action_prev) {
1044 if (repeatable) {
1045 if (!a->macro)
1046 a->macro = vis->macro_operator;
1047 vis->action_prev = *a;
1049 action_reset(a);
1053 void action_reset(Action *a) {
1054 memset(a, 0, sizeof(*a));
1055 a->count = VIS_COUNT_UNKNOWN;
1058 void vis_cancel(Vis *vis) {
1059 action_reset(&vis->action);
1062 void vis_die(Vis *vis, const char *msg, ...) {
1063 va_list ap;
1064 va_start(ap, msg);
1065 vis->ui->die(vis->ui, msg, ap);
1066 va_end(ap);
1069 const char *vis_keys_next(Vis *vis, const char *keys) {
1070 if (!keys || !*keys)
1071 return NULL;
1072 TermKeyKey key;
1073 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1074 const char *next = NULL;
1075 /* first try to parse a special key of the form <Key> */
1076 if (*keys == '<' && keys[1] && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1077 return next+1;
1078 if (strncmp(keys, "<vis-", 5) == 0) {
1079 const char *start = keys + 1, *end = start;
1080 while (*end && *end != '>')
1081 end++;
1082 if (end > start && end - start - 1 < VIS_KEY_LENGTH_MAX && *end == '>') {
1083 char key[VIS_KEY_LENGTH_MAX];
1084 memcpy(key, start, end - start);
1085 key[end - start] = '\0';
1086 if (map_get(vis->actions, key))
1087 return end + 1;
1090 if (ISUTF8(*keys))
1091 keys++;
1092 while (!ISUTF8(*keys))
1093 keys++;
1094 return keys;
1097 long vis_keys_codepoint(Vis *vis, const char *keys) {
1098 long codepoint = -1;
1099 const char *next;
1100 TermKeyKey key;
1101 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1103 if (!keys[0])
1104 return -1;
1105 if (keys[0] == '<' && !keys[1])
1106 return '<';
1108 if (keys[0] == '<' && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
1109 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1110 else if ((next = termkey_strpkey(termkey, keys, &key, TERMKEY_FORMAT_VIM)))
1111 codepoint = (key.type == TERMKEY_TYPE_UNICODE) ? key.code.codepoint : -1;
1113 if (codepoint != -1) {
1114 if (key.modifiers == TERMKEY_KEYMOD_CTRL)
1115 codepoint &= 0x1f;
1116 return codepoint;
1119 if (!next || key.type != TERMKEY_TYPE_KEYSYM)
1120 return -1;
1122 const int keysym[] = {
1123 TERMKEY_SYM_ENTER, '\n',
1124 TERMKEY_SYM_TAB, '\t',
1125 TERMKEY_SYM_BACKSPACE, '\b',
1126 TERMKEY_SYM_ESCAPE, 0x1b,
1127 TERMKEY_SYM_DELETE, 0x7f,
1131 for (const int *k = keysym; k[0]; k += 2) {
1132 if (key.code.sym == k[0])
1133 return k[1];
1136 return -1;
1139 bool vis_keys_utf8(Vis *vis, const char *keys, char utf8[static UTFmax+1]) {
1140 Rune rune = vis_keys_codepoint(vis, keys);
1141 if (rune == (Rune)-1)
1142 return false;
1143 size_t len = runetochar(utf8, &rune);
1144 utf8[len] = '\0';
1145 return true;
1148 typedef struct {
1149 Vis *vis;
1150 size_t len; // length of the prefix
1151 int count; // how many bindings can complete this prefix
1152 bool angle_bracket; // does the prefix end with '<'
1153 } PrefixCompletion;
1155 static bool isprefix(const char *key, void *value, void *data) {
1156 PrefixCompletion *completion = data;
1157 if (!completion->angle_bracket) {
1158 completion->count++;
1159 } else {
1160 const char *start = key + completion->len;
1161 const char *end = vis_keys_next(completion->vis, start);
1162 if (end && start + 1 == end)
1163 completion->count++;
1165 return completion->count == 1;
1168 static void vis_keys_process(Vis *vis, size_t pos) {
1169 Buffer *buf = &vis->input_queue;
1170 char *keys = buf->data + pos, *start = keys, *cur = keys, *end = keys, *binding_end = keys;;
1171 bool prefix = false;
1172 KeyBinding *binding = NULL;
1174 while (cur && *cur) {
1176 if (!(end = (char*)vis_keys_next(vis, cur))) {
1177 buffer_remove(buf, keys - buf->data, strlen(keys));
1178 return;
1181 char tmp = *end;
1182 *end = '\0';
1183 prefix = false;
1185 for (Mode *global_mode = vis->mode; global_mode && !prefix; global_mode = global_mode->parent) {
1186 for (int global = 0; global < 2 && !prefix; global++) {
1187 Mode *mode = (global || !vis->win) ?
1188 global_mode :
1189 &vis->win->modes[global_mode->id];
1190 if (!mode->bindings)
1191 continue;
1192 /* keep track of longest matching binding */
1193 KeyBinding *match = map_get(mode->bindings, start);
1194 if (match && end > binding_end) {
1195 binding = match;
1196 binding_end = end;
1199 const Map *pmap = map_prefix(mode->bindings, start);
1200 PrefixCompletion completions = {
1201 .vis = vis,
1202 .len = cur - start,
1203 .count = 0,
1204 .angle_bracket = !strcmp(cur, "<"),
1206 map_iterate(pmap, isprefix, &completions);
1208 prefix = (!match && completions.count > 0) ||
1209 ( match && completions.count > 1);
1213 *end = tmp;
1215 if (prefix) {
1216 /* input sofar is ambigious, wait for more */
1217 cur = end;
1218 end = start;
1219 } else if (binding) { /* exact match */
1220 if (binding->action) {
1221 size_t len = binding_end - start;
1222 strcpy(vis->key_prev, vis->key_current);
1223 strncpy(vis->key_current, start, len);
1224 vis->key_current[len] = '\0';
1225 end = (char*)binding->action->func(vis, binding_end, &binding->action->arg);
1226 if (!end) {
1227 end = start;
1228 break;
1230 start = cur = end;
1231 } else if (binding->alias) {
1232 buffer_remove(buf, start - buf->data, binding_end - start);
1233 buffer_insert0(buf, start - buf->data, binding->alias);
1234 cur = end = start;
1236 binding = NULL;
1237 binding_end = start;
1238 } else { /* no keybinding */
1239 KeyAction *action = NULL;
1240 if (start[0] == '<' && end[-1] == '>') {
1241 /* test for special editor key command */
1242 char tmp = end[-1];
1243 end[-1] = '\0';
1244 action = map_get(vis->actions, start+1);
1245 end[-1] = tmp;
1246 if (action) {
1247 size_t len = end - start;
1248 strcpy(vis->key_prev, vis->key_current);
1249 strncpy(vis->key_current, start, len);
1250 vis->key_current[len] = '\0';
1251 end = (char*)action->func(vis, end, &action->arg);
1252 if (!end) {
1253 end = start;
1254 break;
1258 if (!action && vis->mode->input) {
1259 end = (char*)vis_keys_next(vis, start);
1260 vis->mode->input(vis, start, end - start);
1262 start = cur = end;
1266 buffer_remove(buf, keys - buf->data, end - keys);
1269 void vis_keys_feed(Vis *vis, const char *input) {
1270 if (!input)
1271 return;
1272 Macro macro;
1273 macro_init(&macro);
1274 if (!macro_append(&macro, input))
1275 return;
1276 /* use internal function, to keep Lua based tests which use undo points working */
1277 macro_replay_internal(vis, &macro);
1278 macro_release(&macro);
1281 static void vis_keys_push(Vis *vis, const char *input, size_t pos, bool record) {
1282 if (!input)
1283 return;
1284 if (record && vis->recording)
1285 macro_append(vis->recording, input);
1286 if (vis->macro_operator)
1287 macro_append(vis->macro_operator, input);
1288 if (buffer_append0(&vis->input_queue, input))
1289 vis_keys_process(vis, pos);
1292 static const char *getkey(Vis *vis) {
1293 TermKeyKey key = { 0 };
1294 if (!vis->ui->getkey(vis->ui, &key))
1295 return NULL;
1296 vis_info_hide(vis);
1297 bool use_keymap = vis->mode->id != VIS_MODE_INSERT &&
1298 vis->mode->id != VIS_MODE_REPLACE &&
1299 !vis->keymap_disabled;
1300 vis->keymap_disabled = false;
1301 if (key.type == TERMKEY_TYPE_UNICODE && use_keymap) {
1302 const char *mapped = map_get(vis->keymap, key.utf8);
1303 if (mapped) {
1304 size_t len = strlen(mapped)+1;
1305 if (len <= sizeof(key.utf8))
1306 memcpy(key.utf8, mapped, len);
1310 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1311 if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) {
1312 long args[18];
1313 size_t nargs;
1314 unsigned long cmd;
1315 if (termkey_interpret_csi(termkey, &key, &args[2], &nargs, &cmd) == TERMKEY_RES_KEY) {
1316 args[0] = (long)cmd;
1317 args[1] = nargs;
1318 vis_event_emit(vis, VIS_EVENT_TERM_CSI, args);
1320 return getkey(vis);
1322 termkey_strfkey(termkey, vis->key, sizeof(vis->key), &key, TERMKEY_FORMAT_VIM);
1323 return vis->key;
1326 bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const void *context) {
1327 switch (signum) {
1328 case SIGBUS:
1329 for (File *file = vis->files; file; file = file->next) {
1330 if (text_mmaped(file->text, siginfo->si_addr))
1331 file->truncated = true;
1333 vis->sigbus = true;
1334 if (vis->running)
1335 siglongjmp(vis->sigbus_jmpbuf, 1);
1336 return true;
1337 case SIGINT:
1338 vis->interrupted = true;
1339 return true;
1340 case SIGCONT:
1341 vis->resume = true;
1342 /* fall through */
1343 case SIGWINCH:
1344 vis->need_resize = true;
1345 return true;
1346 case SIGTERM:
1347 case SIGHUP:
1348 vis->terminate = true;
1349 return true;
1351 return false;
1354 int vis_run(Vis *vis) {
1355 if (!vis->windows)
1356 return EXIT_SUCCESS;
1357 if (vis->exit_status != -1)
1358 return vis->exit_status;
1359 vis->running = true;
1361 vis_event_emit(vis, VIS_EVENT_START);
1363 struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL;
1365 sigset_t emptyset;
1366 sigemptyset(&emptyset);
1367 vis_draw(vis);
1368 vis->exit_status = EXIT_SUCCESS;
1370 sigsetjmp(vis->sigbus_jmpbuf, 1);
1372 while (vis->running) {
1373 fd_set fds;
1374 FD_ZERO(&fds);
1375 FD_SET(STDIN_FILENO, &fds);
1377 if (vis->sigbus) {
1378 char *name = NULL;
1379 for (Win *next, *win = vis->windows; win; win = next) {
1380 next = win->next;
1381 if (win->file->truncated) {
1382 free(name);
1383 name = strdup(win->file->name);
1384 vis_window_close(win);
1387 if (!vis->windows)
1388 vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1389 else
1390 vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
1391 vis->sigbus = false;
1392 free(name);
1395 if (vis->terminate)
1396 vis_die(vis, "Killed by SIGTERM\n");
1397 if (vis->interrupted) {
1398 vis->interrupted = false;
1399 vis_keys_push(vis, "<C-c>", 0, true);
1400 continue;
1403 if (vis->resume) {
1404 vis_resume(vis);
1405 vis->resume = false;
1408 if (vis->need_resize) {
1409 vis->ui->resize(vis->ui);
1410 vis->need_resize = false;
1413 vis_update(vis);
1414 idle.tv_sec = vis->mode->idle_timeout;
1415 int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
1416 if (r == -1 && errno == EINTR)
1417 continue;
1419 if (r < 0) {
1420 /* TODO save all pending changes to a ~suffixed file */
1421 vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
1424 if (!FD_ISSET(STDIN_FILENO, &fds)) {
1425 if (vis->mode->idle)
1426 vis->mode->idle(vis);
1427 timeout = NULL;
1428 continue;
1431 TermKey *termkey = vis->ui->termkey_get(vis->ui);
1432 termkey_advisereadable(termkey);
1433 const char *key;
1435 while ((key = getkey(vis)))
1436 vis_keys_push(vis, key, 0, true);
1438 if (vis->mode->idle)
1439 timeout = &idle;
1441 return vis->exit_status;
1444 Macro *macro_get(Vis *vis, enum VisRegister id) {
1445 if (id == VIS_MACRO_LAST_RECORDED)
1446 return vis->last_recording;
1447 if (VIS_REG_A <= id && id <= VIS_REG_Z)
1448 id -= VIS_REG_A;
1449 if (id < LENGTH(vis->registers))
1450 return array_get(&vis->registers[id].values, 0);
1451 return NULL;
1454 void macro_operator_record(Vis *vis) {
1455 if (vis->macro_operator)
1456 return;
1457 vis->macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
1458 macro_reset(vis->macro_operator);
1461 void macro_operator_stop(Vis *vis) {
1462 if (!vis->macro_operator)
1463 return;
1464 Macro *dot = macro_get(vis, VIS_REG_DOT);
1465 buffer_put(dot, vis->macro_operator->data, vis->macro_operator->len);
1466 vis->action_prev.macro = dot;
1467 vis->macro_operator = NULL;
1470 bool vis_macro_record(Vis *vis, enum VisRegister id) {
1471 Macro *macro = macro_get(vis, id);
1472 if (vis->recording || !macro)
1473 return false;
1474 if (!(VIS_REG_A <= id && id <= VIS_REG_Z))
1475 macro_reset(macro);
1476 vis->recording = macro;
1477 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1478 return true;
1481 bool vis_macro_record_stop(Vis *vis) {
1482 if (!vis->recording)
1483 return false;
1484 /* XXX: hack to remove last recorded key, otherwise upon replay
1485 * we would start another recording */
1486 if (vis->recording->len > 1) {
1487 vis->recording->len--;
1488 vis->recording->data[vis->recording->len-1] = '\0';
1490 vis->last_recording = vis->recording;
1491 vis->recording = NULL;
1492 vis_event_emit(vis, VIS_EVENT_WIN_STATUS, vis->win);
1493 return true;
1496 bool vis_macro_recording(Vis *vis) {
1497 return vis->recording;
1500 static void macro_replay(Vis *vis, const Macro *macro) {
1501 const Macro *replaying = vis->replaying;
1502 vis->replaying = macro;
1503 macro_replay_internal(vis, macro);
1504 vis->replaying = replaying;
1507 static void macro_replay_internal(Vis *vis, const Macro *macro) {
1508 size_t pos = buffer_length0(&vis->input_queue);
1509 for (char *key = macro->data, *next; key; key = next) {
1510 char tmp;
1511 next = (char*)vis_keys_next(vis, key);
1512 if (next) {
1513 tmp = *next;
1514 *next = '\0';
1517 vis_keys_push(vis, key, pos, false);
1519 if (next)
1520 *next = tmp;
1524 bool vis_macro_replay(Vis *vis, enum VisRegister id) {
1525 if (id == VIS_REG_SEARCH)
1526 return vis_motion(vis, VIS_MOVE_SEARCH_REPEAT_FORWARD);
1527 if (id == VIS_REG_COMMAND) {
1528 const char *cmd = register_get(vis, &vis->registers[id], NULL);
1529 return vis_cmd(vis, cmd);
1532 Macro *macro = macro_get(vis, id);
1533 if (!macro || macro == vis->recording)
1534 return false;
1535 int count = vis_count_get_default(vis, 1);
1536 vis_cancel(vis);
1537 for (int i = 0; i < count; i++)
1538 macro_replay(vis, macro);
1539 Win *win = vis->win;
1540 if (win)
1541 vis_file_snapshot(vis, win->file);
1542 return true;
1545 void vis_repeat(Vis *vis) {
1546 const Macro *macro = vis->action_prev.macro;
1547 int count = vis->action.count;
1548 if (count != VIS_COUNT_UNKNOWN)
1549 vis->action_prev.count = count;
1550 else
1551 count = vis->action_prev.count;
1552 vis->action = vis->action_prev;
1553 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
1554 vis_do(vis);
1555 if (macro) {
1556 Mode *mode = vis->mode;
1557 Action action_prev = vis->action_prev;
1558 if (count < 1 || action_prev.op == &vis_operators[VIS_OP_CHANGE])
1559 count = 1;
1560 if (vis->action_prev.op == &vis_operators[VIS_OP_MODESWITCH])
1561 vis->action_prev.count = 1;
1562 for (int i = 0; i < count; i++) {
1563 if (vis->interrupted)
1564 break;
1565 mode_set(vis, mode);
1566 macro_replay(vis, macro);
1568 vis->action_prev = action_prev;
1570 vis_cancel(vis);
1571 Win *win = vis->win;
1572 if (win)
1573 vis_file_snapshot(vis, win->file);
1576 int vis_count_get(Vis *vis) {
1577 return vis->action.count;
1580 int vis_count_get_default(Vis *vis, int def) {
1581 if (vis->action.count == VIS_COUNT_UNKNOWN)
1582 return def;
1583 return vis->action.count;
1586 void vis_count_set(Vis *vis, int count) {
1587 vis->action.count = (count >= 0 ? count : VIS_COUNT_UNKNOWN);
1590 VisCountIterator vis_count_iterator_get(Vis *vis, int def) {
1591 return (VisCountIterator) {
1592 .vis = vis,
1593 .iteration = 0,
1594 .count = vis_count_get_default(vis, def),
1598 VisCountIterator vis_count_iterator_init(Vis *vis, int count) {
1599 return (VisCountIterator) {
1600 .vis = vis,
1601 .iteration = 0,
1602 .count = count,
1606 bool vis_count_iterator_next(VisCountIterator *it) {
1607 if (it->vis->interrupted)
1608 return false;
1609 return it->iteration++ < it->count;
1612 void vis_exit(Vis *vis, int status) {
1613 vis->running = false;
1614 vis->exit_status = status;
1617 void vis_insert_tab(Vis *vis) {
1618 Win *win = vis->win;
1619 if (!win)
1620 return;
1621 if (!vis->expandtab) {
1622 vis_insert_key(vis, "\t", 1);
1623 return;
1625 char spaces[9];
1626 int tabwidth = MIN(vis->tabwidth, LENGTH(spaces) - 1);
1627 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s)) {
1628 size_t pos = view_cursors_pos(s);
1629 int width = text_line_width_get(win->file->text, pos);
1630 int count = tabwidth - (width % tabwidth);
1631 for (int i = 0; i < count; i++)
1632 spaces[i] = ' ';
1633 spaces[count] = '\0';
1634 vis_insert(vis, pos, spaces, count);
1635 view_cursors_scroll_to(s, pos + count);
1639 size_t vis_text_insert_nl(Vis *vis, Text *txt, size_t pos) {
1640 size_t indent_len = 0;
1641 char byte, *indent = NULL;
1642 /* insert second newline at end of file, except if there is already one */
1643 bool eof = pos == text_size(txt);
1644 bool nl2 = eof && !(pos > 0 && text_byte_get(txt, pos-1, &byte) && byte == '\n');
1646 if (vis->autoindent) {
1647 /* copy leading white space of current line */
1648 size_t begin = text_line_begin(txt, pos);
1649 size_t start = text_line_start(txt, begin);
1650 size_t end = text_line_end(txt, start);
1651 if (start > pos)
1652 start = pos;
1653 indent_len = start >= begin ? start-begin : 0;
1654 if (start == end) {
1655 pos = begin;
1656 } else {
1657 indent = malloc(indent_len+1);
1658 if (indent)
1659 indent_len = text_bytes_get(txt, begin, indent_len, indent);
1663 text_insert(txt, pos, "\n", 1);
1664 if (eof) {
1665 if (nl2)
1666 text_insert(txt, text_size(txt), "\n", 1);
1667 else
1668 pos--; /* place cursor before, not after nl */
1670 pos++;
1672 if (indent)
1673 text_insert(txt, pos, indent, indent_len);
1674 free(indent);
1675 return pos + indent_len;
1678 void vis_insert_nl(Vis *vis) {
1679 Win *win = vis->win;
1680 if (!win)
1681 return;
1682 View *view = win->view;
1683 Text *txt = win->file->text;
1684 for (Selection *s = view_selections(view); s; s = view_selections_next(s)) {
1685 size_t pos = view_cursors_pos(s);
1686 size_t newpos = vis_text_insert_nl(vis, txt, pos);
1687 /* This is a bit of a hack to fix cursor positioning when
1688 * inserting a new line at the start of the view port.
1689 * It has the effect of reseting the mark used by the view
1690 * code to keep track of the start of the visible region.
1692 view_cursors_to(s, pos);
1693 view_cursors_to(s, newpos);
1695 vis_window_invalidate(win);
1698 Regex *vis_regex(Vis *vis, const char *pattern) {
1699 if (!pattern && !(pattern = register_get(vis, &vis->registers[VIS_REG_SEARCH], NULL)))
1700 return NULL;
1701 Regex *regex = text_regex_new();
1702 if (!regex)
1703 return NULL;
1704 int cflags = REG_EXTENDED|REG_NEWLINE|(REG_ICASE*vis->ignorecase);
1705 if (text_regex_compile(regex, pattern, cflags) != 0) {
1706 text_regex_free(regex);
1707 return NULL;
1709 register_put0(vis, &vis->registers[VIS_REG_SEARCH], pattern);
1710 return regex;
1713 int vis_pipe(Vis *vis, File *file, Filerange *range, const char *argv[],
1714 void *stdout_context, ssize_t (*read_stdout)(void *stdout_context, char *data, size_t len),
1715 void *stderr_context, ssize_t (*read_stderr)(void *stderr_context, char *data, size_t len)) {
1717 /* if an invalid range was given, stdin (i.e. key board input) is passed
1718 * through the external command. */
1719 Text *text = file->text;
1720 int pin[2], pout[2], perr[2], status = -1;
1721 bool interactive = !text_range_valid(range);
1722 Filerange rout = interactive ? text_range_new(0, 0) : *range;
1724 if (pipe(pin) == -1)
1725 return -1;
1726 if (pipe(pout) == -1) {
1727 close(pin[0]);
1728 close(pin[1]);
1729 return -1;
1732 if (pipe(perr) == -1) {
1733 close(pin[0]);
1734 close(pin[1]);
1735 close(pout[0]);
1736 close(pout[1]);
1737 return -1;
1740 vis->ui->terminal_save(vis->ui);
1741 pid_t pid = fork();
1743 if (pid == -1) {
1744 close(pin[0]);
1745 close(pin[1]);
1746 close(pout[0]);
1747 close(pout[1]);
1748 close(perr[0]);
1749 close(perr[1]);
1750 vis_info_show(vis, "fork failure: %s", strerror(errno));
1751 return -1;
1752 } else if (pid == 0) { /* child i.e filter */
1753 sigset_t sigterm_mask;
1754 sigemptyset(&sigterm_mask);
1755 sigaddset(&sigterm_mask, SIGTERM);
1756 if (sigprocmask(SIG_UNBLOCK, &sigterm_mask, NULL) == -1) {
1757 fprintf(stderr, "failed to reset signal mask");
1758 exit(EXIT_FAILURE);
1761 int null = open("/dev/null", O_RDWR);
1762 if (null == -1) {
1763 fprintf(stderr, "failed to open /dev/null");
1764 exit(EXIT_FAILURE);
1767 if (!interactive) {
1768 /* If we have nothing to write, let stdin point to
1769 * /dev/null instead of a pipe which is immediately
1770 * closed. Some programs behave differently when used
1771 * in a pipeline.
1773 if (text_range_size(range) == 0)
1774 dup2(null, STDIN_FILENO);
1775 else
1776 dup2(pin[0], STDIN_FILENO);
1779 close(pin[0]);
1780 close(pin[1]);
1781 if (interactive) {
1782 dup2(STDERR_FILENO, STDOUT_FILENO);
1783 /* For some reason the first byte written by the
1784 * interactive application is not being displayed.
1785 * It probably has something to do with the terminal
1786 * state change. By writing a dummy byte ourself we
1787 * ensure that the complete output is visible.
1789 while(write(STDOUT_FILENO, " ", 1) == -1 && errno == EINTR);
1790 } else if (read_stdout) {
1791 dup2(pout[1], STDOUT_FILENO);
1792 } else {
1793 dup2(null, STDOUT_FILENO);
1795 close(pout[1]);
1796 close(pout[0]);
1797 if (!interactive) {
1798 if (read_stderr)
1799 dup2(perr[1], STDERR_FILENO);
1800 else
1801 dup2(null, STDERR_FILENO);
1803 close(perr[0]);
1804 close(perr[1]);
1805 close(null);
1807 if (file->name) {
1808 char *name = strrchr(file->name, '/');
1809 setenv("vis_filepath", file->name, 1);
1810 setenv("vis_filename", name ? name+1 : file->name, 1);
1813 if (!argv[1])
1814 execlp(vis->shell, vis->shell, "-c", argv[0], (char*)NULL);
1815 else
1816 execvp(argv[0], (char* const*)argv);
1817 fprintf(stderr, "exec failure: %s", strerror(errno));
1818 exit(EXIT_FAILURE);
1821 vis->interrupted = false;
1823 close(pin[0]);
1824 close(pout[1]);
1825 close(perr[1]);
1827 if (fcntl(pout[0], F_SETFL, O_NONBLOCK) == -1 ||
1828 fcntl(perr[0], F_SETFL, O_NONBLOCK) == -1)
1829 goto err;
1831 fd_set rfds, wfds;
1833 do {
1834 if (vis->interrupted) {
1835 kill(0, SIGTERM);
1836 break;
1839 FD_ZERO(&rfds);
1840 FD_ZERO(&wfds);
1841 if (pin[1] != -1)
1842 FD_SET(pin[1], &wfds);
1843 if (pout[0] != -1)
1844 FD_SET(pout[0], &rfds);
1845 if (perr[0] != -1)
1846 FD_SET(perr[0], &rfds);
1848 if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) {
1849 if (errno == EINTR)
1850 continue;
1851 vis_info_show(vis, "Select failure");
1852 break;
1855 if (pin[1] != -1 && FD_ISSET(pin[1], &wfds)) {
1856 Filerange junk = rout;
1857 if (junk.end > junk.start + PIPE_BUF)
1858 junk.end = junk.start + PIPE_BUF;
1859 ssize_t len = text_write_range(text, &junk, pin[1]);
1860 if (len > 0) {
1861 rout.start += len;
1862 if (text_range_size(&rout) == 0) {
1863 close(pout[1]);
1864 pout[1] = -1;
1866 } else {
1867 close(pin[1]);
1868 pin[1] = -1;
1869 if (len == -1)
1870 vis_info_show(vis, "Error writing to external command");
1874 if (pout[0] != -1 && FD_ISSET(pout[0], &rfds)) {
1875 char buf[BUFSIZ];
1876 ssize_t len = read(pout[0], buf, sizeof buf);
1877 if (len > 0) {
1878 if (read_stdout)
1879 (*read_stdout)(stdout_context, buf, len);
1880 } else if (len == 0) {
1881 close(pout[0]);
1882 pout[0] = -1;
1883 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1884 vis_info_show(vis, "Error reading from filter stdout");
1885 close(pout[0]);
1886 pout[0] = -1;
1890 if (perr[0] != -1 && FD_ISSET(perr[0], &rfds)) {
1891 char buf[BUFSIZ];
1892 ssize_t len = read(perr[0], buf, sizeof buf);
1893 if (len > 0) {
1894 if (read_stderr)
1895 (*read_stderr)(stderr_context, buf, len);
1896 } else if (len == 0) {
1897 close(perr[0]);
1898 perr[0] = -1;
1899 } else if (errno != EINTR && errno != EWOULDBLOCK) {
1900 vis_info_show(vis, "Error reading from filter stderr");
1901 close(perr[0]);
1902 perr[0] = -1;
1906 } while (pin[1] != -1 || pout[0] != -1 || perr[0] != -1);
1908 err:
1909 if (pin[1] != -1)
1910 close(pin[1]);
1911 if (pout[0] != -1)
1912 close(pout[0]);
1913 if (perr[0] != -1)
1914 close(perr[0]);
1916 for (;;) {
1917 if (vis->interrupted)
1918 kill(0, SIGTERM);
1919 pid_t died = waitpid(pid, &status, 0);
1920 if ((died == -1 && errno == ECHILD) || pid == died)
1921 break;
1924 /* clear any pending SIGTERM */
1925 struct sigaction sigterm_ignore, sigterm_old;
1926 sigterm_ignore.sa_handler = SIG_IGN;
1927 sigterm_ignore.sa_flags = 0;
1928 sigemptyset(&sigterm_ignore.sa_mask);
1930 sigaction(SIGTERM, &sigterm_ignore, &sigterm_old);
1931 sigaction(SIGTERM, &sigterm_old, NULL);
1933 vis->interrupted = false;
1934 vis->ui->terminal_restore(vis->ui);
1936 return status;
1939 static ssize_t read_buffer(void *context, char *data, size_t len) {
1940 buffer_append(context, data, len);
1941 return len;
1944 int vis_pipe_collect(Vis *vis, File *file, Filerange *range, const char *argv[], char **out, char **err) {
1945 Buffer bufout, buferr;
1946 buffer_init(&bufout);
1947 buffer_init(&buferr);
1948 int status = vis_pipe(vis, file, range, argv,
1949 &bufout, out ? read_buffer : NULL,
1950 &buferr, err ? read_buffer : NULL);
1951 buffer_terminate(&bufout);
1952 buffer_terminate(&buferr);
1953 if (out)
1954 *out = buffer_move(&bufout);
1955 if (err)
1956 *err = buffer_move(&buferr);
1957 buffer_release(&bufout);
1958 buffer_release(&buferr);
1959 return status;
1962 bool vis_cmd(Vis *vis, const char *cmdline) {
1963 if (!cmdline)
1964 return true;
1965 while (*cmdline == ':')
1966 cmdline++;
1967 char *line = strdup(cmdline);
1968 if (!line)
1969 return false;
1971 size_t len = strlen(line);
1972 while (len > 0 && isspace((unsigned char)line[len-1]))
1973 len--;
1974 line[len] = '\0';
1976 enum SamError err = sam_cmd(vis, line);
1977 if (err != SAM_ERR_OK)
1978 vis_info_show(vis, "%s", sam_error(err));
1979 free(line);
1980 return err == SAM_ERR_OK;
1983 void vis_file_snapshot(Vis *vis, File *file) {
1984 if (!vis->replaying)
1985 text_snapshot(file->text);
1988 Text *vis_text(Vis *vis) {
1989 Win *win = vis->win;
1990 return win ? win->file->text : NULL;
1993 View *vis_view(Vis *vis) {
1994 Win *win = vis->win;
1995 return win ? win->view : NULL;
1998 Win *vis_window(Vis *vis) {
1999 return vis->win;
2002 bool vis_get_autoindent(const Vis *vis) {
2003 return vis->autoindent;