vis: do not crash when using vis:command without active window
[vis.git] / vis.c
blobeb1cce7d1c513197968596e54086888e867a6857
1 #include <locale.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <strings.h>
6 #include <signal.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <ctype.h>
13 #include <time.h>
14 #include <regex.h>
15 #include <sys/select.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <sys/stat.h>
19 #include <sys/ioctl.h>
20 #include <sys/mman.h>
21 #include <pwd.h>
22 #include <termkey.h>
24 #include "vis.h"
25 #include "text-util.h"
26 #include "text-motions.h"
27 #include "text-objects.h"
28 #include "util.h"
29 #include "vis-core.h"
31 /* enable large file optimization for files larger than: */
32 #define LARGE_FILE (1 << 25)
34 static Macro *macro_get(Vis *vis, enum VisRegister);
35 static void macro_replay(Vis *vis, const Macro *macro);
37 /** window / file handling */
39 static void file_free(Vis *vis, File *file) {
40 if (!file)
41 return;
42 if (file->refcount > 1) {
43 --file->refcount;
44 return;
46 if (vis->event && vis->event->file_close)
47 vis->event->file_close(vis, file);
48 text_free(file->text);
49 free((char*)file->name);
51 if (file->prev)
52 file->prev->next = file->next;
53 if (file->next)
54 file->next->prev = file->prev;
55 if (vis->files == file)
56 vis->files = file->next;
57 free(file);
60 static File *file_new_text(Vis *vis, Text *text) {
61 File *file = calloc(1, sizeof(*file));
62 if (!file)
63 return NULL;
64 file->text = text;
65 file->stat = text_stat(text);
66 if (vis->files)
67 vis->files->prev = file;
68 file->next = vis->files;
69 vis->files = file;
70 return file;
73 static File *file_new(Vis *vis, const char *filename) {
74 if (filename) {
75 /* try to detect whether the same file is already open in another window
76 * TODO: do this based on inodes */
77 for (File *file = vis->files; file; file = file->next) {
78 if (file->name && strcmp(file->name, filename) == 0) {
79 return file;
84 Text *text = text_load(filename);
85 if (!text && filename && errno == ENOENT)
86 text = text_load(NULL);
87 if (!text)
88 return NULL;
90 File *file = file_new_text(vis, text);
91 if (!file) {
92 text_free(text);
93 return NULL;
96 if (filename)
97 file->name = strdup(filename);
98 if (vis->event && vis->event->file_open)
99 vis->event->file_open(vis, file);
100 return file;
103 static File *file_new_internal(Vis *vis, const char *filename) {
104 File *file = file_new(vis, filename);
105 if (file) {
106 file->refcount = 1;
107 file->internal = true;
109 return file;
112 void vis_window_name(Win *win, const char *filename) {
113 File *file = win->file;
114 if (filename != file->name) {
115 free((char*)file->name);
116 file->name = filename ? strdup(filename) : NULL;
120 static void windows_invalidate(Vis *vis, size_t start, size_t end) {
121 for (Win *win = vis->windows; win; win = win->next) {
122 if (vis->win != win && vis->win->file == win->file) {
123 Filerange view = view_viewport_get(win->view);
124 if ((view.start <= start && start <= view.end) ||
125 (view.start <= end && end <= view.end))
126 view_draw(win->view);
129 view_draw(vis->win->view);
132 void window_selection_save(Win *win) {
133 File *file = win->file;
134 Filerange sel = view_cursors_selection_get(view_cursors(win->view));
135 file->marks[VIS_MARK_SELECTION_START] = text_mark_set(file->text, sel.start);
136 file->marks[VIS_MARK_SELECTION_END] = text_mark_set(file->text, sel.end);
139 static void window_free(Win *win) {
140 if (!win)
141 return;
142 Vis *vis = win->vis;
143 for (Win *other = vis->windows; other; other = other->next) {
144 if (other->parent == win)
145 other->parent = NULL;
147 if (vis->ui)
148 vis->ui->window_free(win->ui);
149 view_free(win->view);
150 for (size_t i = 0; i < LENGTH(win->modes); i++)
151 map_free(win->modes[i].bindings);
152 ringbuf_free(win->jumplist);
153 free(win);
156 Win *window_new_file(Vis *vis, File *file) {
157 Win *win = calloc(1, sizeof(Win));
158 if (!win)
159 return NULL;
160 win->vis = vis;
161 win->file = file;
162 win->jumplist = ringbuf_alloc(31);
163 win->view = view_new(file->text, vis->lua);
164 win->ui = vis->ui->window_new(vis->ui, win->view, file, UI_OPTION_STATUSBAR);
165 if (!win->jumplist || !win->view || !win->ui) {
166 window_free(win);
167 return NULL;
169 file->refcount++;
170 view_tabwidth_set(win->view, vis->tabwidth);
172 if (text_size(file->text) > LARGE_FILE) {
173 enum UiOption opt = view_options_get(win->view);
174 opt |= UI_OPTION_LARGE_FILE;
175 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
176 view_options_set(win->view, opt);
179 if (vis->windows)
180 vis->windows->prev = win;
181 win->next = vis->windows;
182 vis->windows = win;
183 vis->win = win;
184 vis->ui->window_focus(win->ui);
185 for (size_t i = 0; i < LENGTH(win->modes); i++)
186 win->modes[i].parent = &vis_modes[i];
187 if (vis->event && vis->event->win_open)
188 vis->event->win_open(vis, win);
189 return win;
192 bool vis_window_reload(Win *win) {
193 const char *name = win->file->name;
194 if (!name)
195 return false; /* can't reload unsaved file */
196 /* temporarily unset file name, otherwise file_new returns the same File */
197 win->file->name = NULL;
198 File *file = file_new(win->vis, name);
199 win->file->name = name;
200 if (!file)
201 return false;
202 file_free(win->vis, win->file);
203 file->refcount = 1;
204 win->file = file;
205 win->ui->reload(win->ui, file);
206 return true;
209 bool vis_window_split(Win *original) {
210 Win *win = window_new_file(original->vis, original->file);
211 if (!win)
212 return false;
213 for (size_t i = 0; i < LENGTH(win->modes); i++) {
214 if (original->modes[i].bindings)
215 win->modes[i].bindings = map_new();
216 if (win->modes[i].bindings)
217 map_copy(win->modes[i].bindings, original->modes[i].bindings);
219 win->file = original->file;
220 win->file->refcount++;
221 view_syntax_set(win->view, view_syntax_get(original->view));
222 view_options_set(win->view, view_options_get(original->view));
223 view_cursor_to(win->view, view_cursor_get(original->view));
224 vis_draw(win->vis);
225 return true;
228 void vis_window_next(Vis *vis) {
229 Win *sel = vis->win;
230 if (!sel)
231 return;
232 vis->win = vis->win->next;
233 if (!vis->win)
234 vis->win = vis->windows;
235 vis->ui->window_focus(vis->win->ui);
238 void vis_window_prev(Vis *vis) {
239 Win *sel = vis->win;
240 if (!sel)
241 return;
242 vis->win = vis->win->prev;
243 if (!vis->win)
244 for (vis->win = vis->windows; vis->win->next; vis->win = vis->win->next);
245 vis->ui->window_focus(vis->win->ui);
248 void vis_draw(Vis *vis) {
249 vis->ui->draw(vis->ui);
252 void vis_redraw(Vis *vis) {
253 vis->ui->redraw(vis->ui);
256 void vis_update(Vis *vis) {
257 for (Win *win = vis->windows; win; win = win->next)
258 view_update(win->view);
259 view_update(vis->win->view);
260 vis->ui->update(vis->ui);
263 void vis_suspend(Vis *vis) {
264 vis->ui->suspend(vis->ui);
267 bool vis_window_new(Vis *vis, const char *filename) {
268 File *file = file_new(vis, filename);
269 if (!file)
270 return false;
271 Win *win = window_new_file(vis, file);
272 if (!win) {
273 file_free(vis, file);
274 return false;
277 vis_window_name(win, filename);
278 vis_draw(vis);
280 return true;
283 bool vis_window_closable(Win *win) {
284 if (!text_modified(win->file->text))
285 return true;
286 return win->file->refcount > 1;
289 void vis_window_close(Win *win) {
290 Vis *vis = win->vis;
291 if (vis->event && vis->event->win_close)
292 vis->event->win_close(vis, win);
293 file_free(vis, win->file);
294 if (win->prev)
295 win->prev->next = win->next;
296 if (win->next)
297 win->next->prev = win->prev;
298 if (vis->windows == win)
299 vis->windows = win->next;
300 if (vis->win == win)
301 vis->win = win->next ? win->next : win->prev;
302 if (win == vis->message_window)
303 vis->message_window = NULL;
304 window_free(win);
305 if (vis->win)
306 vis->ui->window_focus(vis->win->ui);
307 vis_draw(vis);
310 Vis *vis_new(Ui *ui, VisEvent *event) {
311 if (!ui)
312 return NULL;
313 Vis *vis = calloc(1, sizeof(Vis));
314 if (!vis)
315 return NULL;
316 vis->ui = ui;
317 vis->ui->init(vis->ui, vis);
318 vis->tabwidth = 8;
319 vis->expandtab = false;
320 vis->registers[VIS_REG_BLACKHOLE].type = REGISTER_BLACKHOLE;
321 vis->registers[VIS_REG_CLIPBOARD].type = REGISTER_CLIPBOARD;
322 action_reset(&vis->action);
323 if (!(vis->command_file = file_new_internal(vis, NULL)))
324 goto err;
325 if (!(vis->search_file = file_new_internal(vis, NULL)))
326 goto err;
327 if (!(vis->keymap = map_new()))
328 goto err;
329 vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL];
330 vis->event = event;
331 if (event && event->vis_start)
332 event->vis_start(vis);
333 vis->ui->start(vis->ui);
334 return vis;
335 err:
336 vis_free(vis);
337 return NULL;
340 void vis_free(Vis *vis) {
341 if (!vis)
342 return;
343 if (vis->event && vis->event->vis_quit)
344 vis->event->vis_quit(vis);
345 vis->event = NULL;
346 while (vis->windows)
347 vis_window_close(vis->windows);
348 file_free(vis, vis->command_file);
349 file_free(vis, vis->search_file);
350 for (int i = 0; i < LENGTH(vis->registers); i++)
351 register_release(&vis->registers[i]);
352 vis->ui->free(vis->ui);
353 map_free(vis->cmds);
354 map_free(vis->options);
355 map_free(vis->actions);
356 map_free(vis->keymap);
357 buffer_release(&vis->input_queue);
358 for (int i = 0; i < VIS_MODE_INVALID; i++)
359 map_free(vis_modes[i].bindings);
360 array_release_full(&vis->motions);
361 array_release_full(&vis->textobjects);
362 free(vis);
365 void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) {
366 text_insert(vis->win->file->text, pos, data, len);
367 windows_invalidate(vis, pos, pos + len);
370 void vis_insert_key(Vis *vis, const char *data, size_t len) {
371 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
372 size_t pos = view_cursors_pos(c);
373 vis_insert(vis, pos, data, len);
374 view_cursors_scroll_to(c, pos + len);
378 void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) {
379 Text *txt = vis->win->file->text;
380 Iterator it = text_iterator_get(txt, pos);
381 int chars = text_char_count(data, len);
382 for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n'; )
383 text_iterator_char_next(&it, NULL);
385 text_delete(txt, pos, it.pos - pos);
386 vis_insert(vis, pos, data, len);
389 void vis_replace_key(Vis *vis, const char *data, size_t len) {
390 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
391 size_t pos = view_cursors_pos(c);
392 vis_replace(vis, pos, data, len);
393 view_cursors_scroll_to(c, pos + len);
397 void vis_delete(Vis *vis, size_t pos, size_t len) {
398 text_delete(vis->win->file->text, pos, len);
399 windows_invalidate(vis, pos, pos + len);
402 bool vis_action_register(Vis *vis, const KeyAction *action) {
403 if (!vis->actions)
404 vis->actions = map_new();
405 if (!vis->actions)
406 return false;
407 return map_put(vis->actions, action->name, action);
410 bool vis_keymap_add(Vis *vis, const char *key, const char *mapping) {
411 return map_put(vis->keymap, key, mapping);
414 static void window_jumplist_add(Win *win, size_t pos) {
415 Mark mark = text_mark_set(win->file->text, pos);
416 if (mark && win->jumplist)
417 ringbuf_add(win->jumplist, mark);
420 static void window_jumplist_invalidate(Win *win) {
421 if (win->jumplist)
422 ringbuf_invalidate(win->jumplist);
425 void action_do(Vis *vis, Action *a) {
426 Win *win = vis->win;
427 Text *txt = win->file->text;
428 View *view = win->view;
430 if (a->op == &vis_operators[VIS_OP_FILTER] && !vis->mode->visual)
431 vis_mode_switch(vis, VIS_MODE_VISUAL_LINE);
433 int count = MAX(a->count, 1);
434 bool repeatable = a->op && !vis->macro_operator;
435 bool multiple_cursors = view_cursors_count(view) > 1;
436 bool linewise = !(a->type & CHARWISE) && (
437 a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) ||
438 vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]);
440 for (Cursor *cursor = view_cursors(view), *next; cursor; cursor = next) {
442 next = view_cursors_next(cursor);
443 size_t pos = view_cursors_pos(cursor);
444 Register *reg = multiple_cursors ? view_cursors_register(cursor) : a->reg;
445 if (!reg)
446 reg = &vis->registers[win->file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT];
448 OperatorContext c = {
449 .count = count,
450 .pos = pos,
451 .newpos = EPOS,
452 .range = text_range_empty(),
453 .reg = reg,
454 .linewise = linewise,
455 .arg = &a->arg,
458 if (a->movement) {
459 size_t start = pos;
460 for (int i = 0; i < count; i++) {
461 if (a->movement->txt)
462 pos = a->movement->txt(txt, pos);
463 else if (a->movement->cur)
464 pos = a->movement->cur(cursor);
465 else if (a->movement->file)
466 pos = a->movement->file(vis, vis->win->file, pos);
467 else if (a->movement->vis)
468 pos = a->movement->vis(vis, txt, pos);
469 else if (a->movement->view)
470 pos = a->movement->view(vis, view);
471 else if (a->movement->win)
472 pos = a->movement->win(vis, win, pos);
473 else if (a->movement->user)
474 pos = a->movement->user(vis, win, a->movement->data, pos);
475 if (pos == EPOS || a->movement->type & IDEMPOTENT)
476 break;
479 if (pos == EPOS) {
480 c.range.start = start;
481 c.range.end = start;
482 pos = start;
483 } else {
484 c.range = text_range_new(start, pos);
485 c.newpos = pos;
488 if (!a->op) {
489 if (a->movement->type & CHARWISE)
490 view_cursors_scroll_to(cursor, pos);
491 else
492 view_cursors_to(cursor, pos);
493 if (vis->mode->visual)
494 c.range = view_cursors_selection_get(cursor);
495 if (a->movement->type & JUMP)
496 window_jumplist_add(win, pos);
497 else
498 window_jumplist_invalidate(win);
499 } else if (a->movement->type & INCLUSIVE) {
500 c.range.end = text_char_next(txt, c.range.end);
502 } else if (a->textobj) {
503 if (vis->mode->visual)
504 c.range = view_cursors_selection_get(cursor);
505 else
506 c.range.start = c.range.end = pos;
507 for (int i = 0; i < count; i++) {
508 Filerange r = text_range_empty();
509 if (a->textobj->txt)
510 r = a->textobj->txt(txt, pos);
511 else if (a->textobj->vis)
512 r = a->textobj->vis(vis, txt, pos);
513 else if (a->textobj->user)
514 r = a->textobj->user(vis, win, a->textobj->data, pos);
515 if (!text_range_valid(&r))
516 break;
517 if (a->textobj->type & OUTER) {
518 r.start--;
519 r.end++;
522 if (a->textobj->type & SPLIT)
523 c.range = r;
524 else
525 c.range = text_range_union(&c.range, &r);
527 if (i < count - 1)
528 pos = c.range.end + 1;
530 } else if (vis->mode->visual) {
531 c.range = view_cursors_selection_get(cursor);
532 if (!text_range_valid(&c.range))
533 c.range.start = c.range.end = pos;
536 if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL])
537 c.range = text_range_linewise(txt, &c.range);
538 if (vis->mode->visual) {
539 view_cursors_selection_set(cursor, &c.range);
540 if (vis->mode == &vis_modes[VIS_MODE_VISUAL] || a->textobj)
541 view_cursors_selection_sync(cursor);
544 if (a->op) {
545 size_t pos = a->op->func(vis, txt, &c);
546 if (pos == EPOS) {
547 view_cursors_dispose(cursor);
548 } else if (pos <= text_size(txt)) {
549 /* moving the cursor will affect the selection.
550 * because we want to be able to later restore
551 * the old selection we update it again before
552 * leaving visual mode.
554 Filerange sel = view_cursors_selection_get(cursor);
555 view_cursors_to(cursor, pos);
556 if (vis->mode->visual) {
557 if (sel.start == EPOS && sel.end == EPOS)
558 sel = c.range;
559 else if (sel.start == EPOS)
560 sel = text_range_new(c.range.start, sel.end);
561 else if (sel.end == EPOS)
562 sel = text_range_new(c.range.start, sel.start);
563 if (vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE])
564 sel = text_range_linewise(txt, &sel);
565 view_cursors_selection_set(cursor, &sel);
571 if (a->op) {
572 /* we do not support visual repeat, still do something resonable */
573 if (vis->mode->visual && !a->movement && !a->textobj)
574 a->movement = &vis_motions[VIS_MOVE_NOP];
576 /* operator implementations must not change the mode,
577 * they might get called multiple times (once for every cursor)
579 if (a->op == &vis_operators[VIS_OP_INSERT] || a->op == &vis_operators[VIS_OP_CHANGE]) {
580 vis_mode_switch(vis, VIS_MODE_INSERT);
581 } else if (a->op == &vis_operators[VIS_OP_REPLACE]) {
582 vis_mode_switch(vis, VIS_MODE_REPLACE);
583 } else if (a->op == &vis_operators[VIS_OP_FILTER]) {
584 if (a->arg.s) {
585 vis_mode_switch(vis, VIS_MODE_NORMAL);
586 vis_cmd(vis, a->arg.s);
587 } else {
588 vis_prompt_show(vis, ":'<,'>!");
590 } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) {
591 mode_set(vis, vis->mode_prev);
592 } else if (vis->mode->visual) {
593 vis_mode_switch(vis, VIS_MODE_NORMAL);
595 text_snapshot(txt);
596 vis_draw(vis);
599 if (a != &vis->action_prev) {
600 if (repeatable) {
601 if (!a->macro)
602 a->macro = vis->macro_operator;
603 vis->action_prev = *a;
605 action_reset(a);
609 void action_reset(Action *a) {
610 memset(a, 0, sizeof(*a));
611 a->count = VIS_COUNT_UNKNOWN;
614 void vis_cancel(Vis *vis) {
615 action_reset(&vis->action);
618 void vis_die(Vis *vis, const char *msg, ...) {
619 va_list ap;
620 va_start(ap, msg);
621 vis->ui->die(vis->ui, msg, ap);
622 va_end(ap);
625 const char *vis_keys_next(Vis *vis, const char *keys) {
626 TermKeyKey key;
627 TermKey *termkey = vis->ui->termkey_get(vis->ui);
628 const char *next = NULL;
629 if (!keys)
630 return NULL;
631 /* first try to parse a special key of the form <Key> */
632 if (*keys == '<' && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
633 return next+1;
634 if (*keys == '<') {
635 const char *start = keys + 1, *end = start;
636 while (*end && *end != '>')
637 end++;
638 if (end > start && end - start - 1 < 64 && *end == '>') {
639 char key[64];
640 memcpy(key, start, end - start);
641 key[end - start] = '\0';
642 if (map_get(vis->actions, key))
643 return end + 1;
646 while (!ISUTF8(*keys))
647 keys++;
648 return termkey_strpkey(termkey, keys, &key, TERMKEY_FORMAT_VIM);
651 static const char *vis_keys_raw(Vis *vis, Buffer *buf, const char *input) {
652 char *keys = buf->data, *start = keys, *cur = keys, *end;
653 bool prefix = false;
654 KeyBinding *binding = NULL;
656 while (cur && *cur) {
658 if (!(end = (char*)vis_keys_next(vis, cur)))
659 goto err; // XXX: can't parse key this should never happen
661 char tmp = *end;
662 *end = '\0';
663 prefix = false;
664 binding = NULL;
666 for (Mode *mode = vis->mode; mode && !binding && !prefix; mode = mode->parent) {
667 for (int global = 0; global < 2 && !binding && !prefix; global++) {
668 Mode *mode_local = global ? mode : &vis->win->modes[mode->id];
669 if (!mode_local->bindings)
670 continue;
671 binding = map_get(mode_local->bindings, start);
672 /* "<" is never treated as a prefix because it is used to denote
673 * special key symbols */
674 if (strcmp(cur, "<"))
675 prefix = !binding && map_contains(mode_local->bindings, start);
679 *end = tmp;
680 vis->keys = buf;
682 if (binding) { /* exact match */
683 if (binding->action) {
684 end = (char*)binding->action->func(vis, end, &binding->action->arg);
685 if (!end)
686 break;
687 start = cur = end;
688 } else if (binding->alias) {
689 buffer_put0(buf, end);
690 buffer_prepend0(buf, binding->alias);
691 start = cur = buf->data;
693 } else if (prefix) { /* incomplete key binding? */
694 cur = end;
695 } else { /* no keybinding */
696 KeyAction *action = NULL;
697 if (start[0] == '<' && end[-1] == '>') {
698 /* test for special editor key command */
699 char tmp = end[-1];
700 end[-1] = '\0';
701 action = map_get(vis->actions, start+1);
702 end[-1] = tmp;
703 if (action) {
704 end = (char*)action->func(vis, end, &action->arg);
705 if (!end)
706 break;
709 if (!action && vis->mode->input)
710 vis->mode->input(vis, start, end - start);
711 start = cur = end;
715 vis->keys = NULL;
716 buffer_put0(buf, start);
717 return input + (start - keys);
718 err:
719 vis->keys = NULL;
720 buffer_truncate(buf);
721 return input + strlen(input);
724 bool vis_keys_inject(Vis *vis, const char *pos, const char *input) {
725 Buffer *buf = vis->keys;
726 if (!buf)
727 return false;
728 if (pos < buf->data || pos > buf->data + buf->len)
729 return false;
730 size_t off = pos - buf->data;
731 buffer_insert0(buf, off, input);
732 if (vis->macro_operator)
733 macro_append(vis->macro_operator, input);
734 return true;
737 const char *vis_keys_push(Vis *vis, const char *input) {
738 if (!input)
739 return NULL;
741 if (vis->recording)
742 macro_append(vis->recording, input);
743 if (vis->macro_operator)
744 macro_append(vis->macro_operator, input);
746 if (!buffer_append0(&vis->input_queue, input)) {
747 buffer_truncate(&vis->input_queue);
748 return NULL;
751 return vis_keys_raw(vis, &vis->input_queue, input);
754 static const char *getkey(Vis *vis) {
755 const char *key = vis->ui->getkey(vis->ui);
756 if (!key)
757 return NULL;
758 vis_info_hide(vis);
759 if (!vis->mode->input) {
760 const char *mapped = map_get(vis->keymap, key);
761 if (mapped)
762 return mapped;
764 return key;
767 bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const void *context) {
768 switch (signum) {
769 case SIGBUS:
770 for (File *file = vis->files; file; file = file->next) {
771 if (text_sigbus(file->text, siginfo->si_addr))
772 file->truncated = true;
774 vis->sigbus = true;
775 if (vis->running)
776 siglongjmp(vis->sigbus_jmpbuf, 1);
777 return true;
778 case SIGINT:
779 vis->cancel_filter = true;
780 return true;
782 return false;
785 static void vis_args(Vis *vis, int argc, char *argv[]) {
786 char *cmd = NULL;
787 bool end_of_options = false;
788 for (int i = 1; i < argc; i++) {
789 if (argv[i][0] == '-' && !end_of_options) {
790 switch (argv[i][1]) {
791 case '-':
792 end_of_options = true;
793 break;
794 case 'v':
795 vis_die(vis, "vis %s, compiled " __DATE__ " " __TIME__ "\n", VERSION);
796 break;
797 case '\0':
798 break;
799 default:
800 vis_die(vis, "Unknown command option: %s\n", argv[i]);
801 break;
803 } else if (argv[i][0] == '+') {
804 cmd = argv[i] + (argv[i][1] == '/' || argv[i][1] == '?');
805 } else if (!vis_window_new(vis, argv[i])) {
806 vis_die(vis, "Can not load `%s': %s\n", argv[i], strerror(errno));
807 } else if (cmd) {
808 vis_prompt_cmd(vis, cmd);
809 cmd = NULL;
813 if (!vis->windows) {
814 if (!strcmp(argv[argc-1], "-")) {
815 if (!vis_window_new(vis, NULL))
816 vis_die(vis, "Can not create empty buffer\n");
817 ssize_t len = 0;
818 char buf[PIPE_BUF];
819 File *file = vis->win->file;
820 Text *txt = file->text;
821 file->is_stdin = true;
822 while ((len = read(STDIN_FILENO, buf, sizeof buf)) > 0)
823 text_insert(txt, text_size(txt), buf, len);
824 if (len == -1)
825 vis_die(vis, "Can not read from stdin\n");
826 text_snapshot(txt);
827 int fd = open("/dev/tty", O_RDONLY);
828 if (fd == -1)
829 vis_die(vis, "Can not reopen stdin\n");
830 dup2(fd, STDIN_FILENO);
831 close(fd);
832 } else if (!vis_window_new(vis, NULL)) {
833 vis_die(vis, "Can not create empty buffer\n");
835 if (cmd)
836 vis_prompt_cmd(vis, cmd);
840 int vis_run(Vis *vis, int argc, char *argv[]) {
841 vis_args(vis, argc, argv);
843 struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL;
845 sigset_t emptyset;
846 sigemptyset(&emptyset);
847 vis_draw(vis);
848 vis->running = true;
849 vis->exit_status = EXIT_SUCCESS;
851 sigsetjmp(vis->sigbus_jmpbuf, 1);
853 while (vis->running) {
854 fd_set fds;
855 FD_ZERO(&fds);
856 FD_SET(STDIN_FILENO, &fds);
858 if (vis->sigbus) {
859 char *name = NULL;
860 for (Win *next, *win = vis->windows; win; win = next) {
861 next = win->next;
862 if (win->file->truncated) {
863 free(name);
864 name = strdup(win->file->name);
865 vis_window_close(win);
868 if (!vis->windows)
869 vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
870 else
871 vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
872 vis->sigbus = false;
873 free(name);
876 vis_update(vis);
877 idle.tv_sec = vis->mode->idle_timeout;
878 int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
879 if (r == -1 && errno == EINTR)
880 continue;
882 if (r < 0) {
883 /* TODO save all pending changes to a ~suffixed file */
884 vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
887 if (!FD_ISSET(STDIN_FILENO, &fds)) {
888 if (vis->mode->idle)
889 vis->mode->idle(vis);
890 timeout = NULL;
891 continue;
894 TermKey *termkey = vis->ui->termkey_get(vis->ui);
895 termkey_advisereadable(termkey);
896 const char *key;
898 while ((key = getkey(vis)))
899 vis_keys_push(vis, key);
901 if (vis->mode->idle)
902 timeout = &idle;
904 return vis->exit_status;
907 static Macro *macro_get(Vis *vis, enum VisRegister id) {
908 if (id == VIS_MACRO_LAST_RECORDED)
909 return vis->last_recording;
910 if (VIS_REG_A <= id && id <= VIS_REG_Z)
911 id -= VIS_REG_A;
912 if (id < LENGTH(vis->registers))
913 return &vis->registers[id].buf;
914 return NULL;
917 void macro_operator_record(Vis *vis) {
918 vis->macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
919 macro_reset(vis->macro_operator);
922 void macro_operator_stop(Vis *vis) {
923 vis->macro_operator = NULL;
926 bool vis_macro_record(Vis *vis, enum VisRegister id) {
927 Macro *macro = macro_get(vis, id);
928 if (vis->recording || !macro)
929 return false;
930 if (!(VIS_REG_A <= id && id <= VIS_REG_Z))
931 macro_reset(macro);
932 vis->recording = macro;
933 return true;
936 bool vis_macro_record_stop(Vis *vis) {
937 if (!vis->recording)
938 return false;
939 /* XXX: hack to remove last recorded key, otherwise upon replay
940 * we would start another recording */
941 if (vis->recording->len > 1) {
942 vis->recording->len--;
943 vis->recording->data[vis->recording->len-1] = '\0';
945 vis->last_recording = vis->recording;
946 vis->recording = NULL;
947 return true;
950 bool vis_macro_recording(Vis *vis) {
951 return vis->recording;
954 static void macro_replay(Vis *vis, const Macro *macro) {
955 Buffer buf;
956 buffer_init(&buf);
957 buffer_put(&buf, macro->data, macro->len);
958 buffer_append(&buf, "\0", 1);
959 vis_keys_raw(vis, &buf, macro->data);
960 buffer_release(&buf);
963 bool vis_macro_replay(Vis *vis, enum VisRegister id) {
964 if (id == VIS_REG_SEARCH)
965 return vis_motion(vis, VIS_MOVE_SEARCH_NEXT);
966 if (id == VIS_REG_COMMAND) {
967 const char *cmd = register_get(vis, &vis->registers[id], NULL);
968 return vis_cmd(vis, cmd);
971 Macro *macro = macro_get(vis, id);
972 if (!macro || macro == vis->recording)
973 return false;
974 macro_replay(vis, macro);
975 return true;
978 void vis_repeat(Vis *vis) {
979 int count = vis->action.count;
980 Macro *macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
981 Macro *macro_repeat = macro_get(vis, VIS_MACRO_REPEAT);
982 const Macro *macro = vis->action_prev.macro;
983 if (macro == macro_operator) {
984 buffer_put(macro_repeat, macro_operator->data, macro_operator->len);
985 macro = macro_repeat;
986 vis->action_prev.macro = macro;
988 if (count != VIS_COUNT_UNKNOWN)
989 vis->action_prev.count = count;
990 count = vis->action_prev.count;
991 /* for some operators count should be applied only to the macro not the motion */
992 if (vis->action_prev.op == &vis_operators[VIS_OP_INSERT] || vis->action_prev.op == &vis_operators[VIS_OP_REPLACE])
993 vis->action_prev.count = 1;
994 action_do(vis, &vis->action_prev);
995 vis->action_prev.count = count;
996 if (macro) {
997 Mode *mode = vis->mode;
998 Action action_prev = vis->action_prev;
999 count = action_prev.count;
1000 if (count < 1 || action_prev.op == &vis_operators[VIS_OP_CHANGE] || action_prev.op == &vis_operators[VIS_OP_FILTER])
1001 count = 1;
1002 for (int i = 0; i < count; i++) {
1003 mode_set(vis, mode);
1004 macro_replay(vis, macro);
1006 vis->action_prev = action_prev;
1008 vis_cancel(vis);
1011 void vis_mark_set(Vis *vis, enum VisMark mark, size_t pos) {
1012 File *file = vis->win->file;
1013 if (mark < LENGTH(file->marks))
1014 file->marks[mark] = text_mark_set(file->text, pos);
1017 int vis_count_get(Vis *vis) {
1018 return vis->action.count;
1021 int vis_count_get_default(Vis *vis, int def) {
1022 if (vis->action.count == VIS_COUNT_UNKNOWN)
1023 return def;
1024 return vis->action.count;
1027 void vis_count_set(Vis *vis, int count) {
1028 vis->action.count = (count >= 0 ? count : VIS_COUNT_UNKNOWN);
1031 void vis_register_set(Vis *vis, enum VisRegister reg) {
1032 if (reg >= VIS_REG_A && reg <= VIS_REG_Z) {
1033 vis->action.reg = &vis->registers[reg - VIS_REG_A];
1034 vis->action.reg->append = true;
1035 } else if (reg < LENGTH(vis->registers)) {
1036 vis->action.reg = &vis->registers[reg];
1037 vis->action.reg->append = false;
1041 const char *vis_register_get(Vis *vis, enum VisRegister reg, size_t *len) {
1042 if (VIS_REG_A <= reg && reg <= VIS_REG_Z)
1043 reg -= VIS_REG_A;
1044 if (reg < LENGTH(vis->registers))
1045 return register_get(vis, &vis->registers[reg], len);
1046 *len = 0;
1047 return NULL;
1050 void vis_exit(Vis *vis, int status) {
1051 vis->running = false;
1052 vis->exit_status = status;
1055 const char *vis_mode_status(Vis *vis) {
1056 return vis->mode->status;
1059 void vis_insert_tab(Vis *vis) {
1060 if (!vis->expandtab) {
1061 vis_insert_key(vis, "\t", 1);
1062 return;
1064 char spaces[9];
1065 int tabwidth = MIN(vis->tabwidth, LENGTH(spaces) - 1);
1066 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
1067 size_t pos = view_cursors_pos(c);
1068 int width = text_line_width_get(vis->win->file->text, pos);
1069 int count = tabwidth - (width % tabwidth);
1070 for (int i = 0; i < count; i++)
1071 spaces[i] = ' ';
1072 spaces[count] = '\0';
1073 vis_insert(vis, pos, spaces, count);
1074 view_cursors_scroll_to(c, pos + count);
1078 static void copy_indent_from_previous_line(Win *win) {
1079 View *view = win->view;
1080 Text *text = win->file->text;
1081 size_t pos = view_cursor_get(view);
1082 size_t prev_line = text_line_prev(text, pos);
1083 if (pos == prev_line)
1084 return;
1085 size_t begin = text_line_begin(text, prev_line);
1086 size_t start = text_line_start(text, begin);
1087 size_t len = start-begin;
1088 char *buf = malloc(len);
1089 if (!buf)
1090 return;
1091 len = text_bytes_get(text, begin, len, buf);
1092 vis_insert_key(win->vis, buf, len);
1093 free(buf);
1096 void vis_insert_nl(Vis *vis) {
1097 const char *nl = text_newline_char(vis->win->file->text);
1098 vis_insert_key(vis, nl, strlen(nl));
1100 if (vis->autoindent)
1101 copy_indent_from_previous_line(vis->win);
1104 Regex *vis_regex(Vis *vis, const char *pattern) {
1105 if (!pattern && !(pattern = register_get(vis, &vis->registers[VIS_REG_SEARCH], NULL)))
1106 return NULL;
1107 Regex *regex = text_regex_new();
1108 if (!regex)
1109 return NULL;
1110 if (text_regex_compile(regex, pattern, REG_EXTENDED|REG_NEWLINE) != 0) {
1111 text_regex_free(regex);
1112 return NULL;
1114 register_put0(vis, &vis->registers[VIS_REG_SEARCH], pattern);
1115 return regex;
1118 Text *vis_text(Vis *vis) {
1119 return vis->win->file->text;
1122 View *vis_view(Vis *vis) {
1123 return vis->win->view;
1126 Win *vis_window(Vis *vis) {
1127 return vis->win;
1130 Text *vis_file_text(File *file) {
1131 return file->text;
1134 const char *vis_file_name(File *file) {
1135 return file->name;