Convert call sites of text_line_char_{get,set} to text_line_width_{get,set}
[vis.git] / vis.c
blob848180f36eee3f56cfc4d04c0d7927ac768d1c8b
1 /*
2 * Copyright (c) 2014-2015 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <locale.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <string.h>
20 #include <strings.h>
21 #include <signal.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <ctype.h>
28 #include <time.h>
29 #include <regex.h>
30 #include <sys/select.h>
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <sys/stat.h>
34 #include <sys/ioctl.h>
35 #include <sys/mman.h>
36 #include <pwd.h>
37 #include <termkey.h>
39 #include "vis.h"
40 #include "text-util.h"
41 #include "text-motions.h"
42 #include "text-objects.h"
43 #include "util.h"
44 #include "vis-core.h"
46 /* enable large file optimization for files larger than: */
47 #define LARGE_FILE (1 << 25)
49 static Macro *macro_get(Vis *vis, enum VisMacro m);
50 static void macro_replay(Vis *vis, const Macro *macro);
52 /** window / file handling */
54 static void file_free(Vis *vis, File *file) {
55 if (!file)
56 return;
57 if (file->refcount > 1) {
58 --file->refcount;
59 return;
61 if (vis->event && vis->event->file_close)
62 vis->event->file_close(vis, file);
63 text_free(file->text);
64 free((char*)file->name);
66 if (file->prev)
67 file->prev->next = file->next;
68 if (file->next)
69 file->next->prev = file->prev;
70 if (vis->files == file)
71 vis->files = file->next;
72 free(file);
75 static File *file_new_text(Vis *vis, Text *text) {
76 File *file = calloc(1, sizeof(*file));
77 if (!file)
78 return NULL;
79 file->text = text;
80 file->stat = text_stat(text);
81 if (vis->files)
82 vis->files->prev = file;
83 file->next = vis->files;
84 vis->files = file;
85 return file;
88 static File *file_new(Vis *vis, const char *filename) {
89 if (filename) {
90 /* try to detect whether the same file is already open in another window
91 * TODO: do this based on inodes */
92 for (File *file = vis->files; file; file = file->next) {
93 if (file->name && strcmp(file->name, filename) == 0) {
94 return file;
99 Text *text = text_load(filename);
100 if (!text && filename && errno == ENOENT)
101 text = text_load(NULL);
102 if (!text)
103 return NULL;
105 File *file = file_new_text(vis, text);
106 if (!file) {
107 text_free(text);
108 return NULL;
111 if (filename)
112 file->name = strdup(filename);
113 if (vis->event && vis->event->file_open)
114 vis->event->file_open(vis, file);
115 return file;
118 static File *file_new_internal(Vis *vis, const char *filename) {
119 File *file = file_new(vis, filename);
120 if (file) {
121 file->refcount = 1;
122 file->internal = true;
124 return file;
127 void vis_window_name(Win *win, const char *filename) {
128 File *file = win->file;
129 if (filename != file->name) {
130 free((char*)file->name);
131 file->name = filename ? strdup(filename) : NULL;
135 static void windows_invalidate(Vis *vis, size_t start, size_t end) {
136 for (Win *win = vis->windows; win; win = win->next) {
137 if (vis->win != win && vis->win->file == win->file) {
138 Filerange view = view_viewport_get(win->view);
139 if ((view.start <= start && start <= view.end) ||
140 (view.start <= end && end <= view.end))
141 view_draw(win->view);
144 view_draw(vis->win->view);
147 void window_selection_save(Win *win) {
148 File *file = win->file;
149 Filerange sel = view_cursors_selection_get(view_cursors(win->view));
150 file->marks[VIS_MARK_SELECTION_START] = text_mark_set(file->text, sel.start);
151 file->marks[VIS_MARK_SELECTION_END] = text_mark_set(file->text, sel.end);
154 static void window_free(Win *win) {
155 if (!win)
156 return;
157 Vis *vis = win->vis;
158 for (Win *other = vis->windows; other; other = other->next) {
159 if (other->parent == win)
160 other->parent = NULL;
162 if (vis->ui)
163 vis->ui->window_free(win->ui);
164 view_free(win->view);
165 for (size_t i = 0; i < LENGTH(win->modes); i++)
166 map_free(win->modes[i].bindings);
167 ringbuf_free(win->jumplist);
168 free(win);
171 Win *window_new_file(Vis *vis, File *file) {
172 Win *win = calloc(1, sizeof(Win));
173 if (!win)
174 return NULL;
175 win->vis = vis;
176 win->file = file;
177 win->jumplist = ringbuf_alloc(31);
178 win->view = view_new(file->text, vis->lua);
179 win->ui = vis->ui->window_new(vis->ui, win->view, file, UI_OPTION_STATUSBAR);
180 if (!win->jumplist || !win->view || !win->ui) {
181 window_free(win);
182 return NULL;
184 file->refcount++;
185 view_tabwidth_set(win->view, vis->tabwidth);
187 if (text_size(file->text) > LARGE_FILE) {
188 enum UiOption opt = view_options_get(win->view);
189 opt |= UI_OPTION_LARGE_FILE;
190 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
191 view_options_set(win->view, opt);
194 if (vis->windows)
195 vis->windows->prev = win;
196 win->next = vis->windows;
197 vis->windows = win;
198 vis->win = win;
199 vis->ui->window_focus(win->ui);
200 for (size_t i = 0; i < LENGTH(win->modes); i++)
201 win->modes[i].parent = &vis_modes[i];
202 if (vis->event && vis->event->win_open)
203 vis->event->win_open(vis, win);
204 return win;
207 bool vis_window_reload(Win *win) {
208 const char *name = win->file->name;
209 if (!name)
210 return false; /* can't reload unsaved file */
211 /* temporarily unset file name, otherwise file_new returns the same File */
212 win->file->name = NULL;
213 File *file = file_new(win->vis, name);
214 win->file->name = name;
215 if (!file)
216 return false;
217 file_free(win->vis, win->file);
218 file->refcount = 1;
219 win->file = file;
220 win->ui->reload(win->ui, file);
221 return true;
224 bool vis_window_split(Win *original) {
225 Win *win = window_new_file(original->vis, original->file);
226 if (!win)
227 return false;
228 for (size_t i = 0; i < LENGTH(win->modes); i++) {
229 if (original->modes[i].bindings)
230 win->modes[i].bindings = map_new();
231 if (win->modes[i].bindings)
232 map_copy(win->modes[i].bindings, original->modes[i].bindings);
234 win->file = original->file;
235 win->file->refcount++;
236 view_syntax_set(win->view, view_syntax_get(original->view));
237 view_options_set(win->view, view_options_get(original->view));
238 view_cursor_to(win->view, view_cursor_get(original->view));
239 vis_draw(win->vis);
240 return true;
243 void vis_window_next(Vis *vis) {
244 Win *sel = vis->win;
245 if (!sel)
246 return;
247 vis->win = vis->win->next;
248 if (!vis->win)
249 vis->win = vis->windows;
250 vis->ui->window_focus(vis->win->ui);
253 void vis_window_prev(Vis *vis) {
254 Win *sel = vis->win;
255 if (!sel)
256 return;
257 vis->win = vis->win->prev;
258 if (!vis->win)
259 for (vis->win = vis->windows; vis->win->next; vis->win = vis->win->next);
260 vis->ui->window_focus(vis->win->ui);
263 void vis_draw(Vis *vis) {
264 vis->ui->draw(vis->ui);
267 void vis_redraw(Vis *vis) {
268 vis->ui->redraw(vis->ui);
271 void vis_update(Vis *vis) {
272 for (Win *win = vis->windows; win; win = win->next)
273 view_update(win->view);
274 view_update(vis->win->view);
275 vis->ui->update(vis->ui);
278 void vis_suspend(Vis *vis) {
279 vis->ui->suspend(vis->ui);
282 bool vis_window_new(Vis *vis, const char *filename) {
283 File *file = file_new(vis, filename);
284 if (!file)
285 return false;
286 Win *win = window_new_file(vis, file);
287 if (!win) {
288 file_free(vis, file);
289 return false;
292 vis_window_name(win, filename);
293 vis_draw(vis);
295 return true;
298 bool vis_window_closable(Win *win) {
299 if (!text_modified(win->file->text))
300 return true;
301 return win->file->refcount > 1;
304 void vis_window_close(Win *win) {
305 Vis *vis = win->vis;
306 if (vis->event && vis->event->win_close)
307 vis->event->win_close(vis, win);
308 file_free(vis, win->file);
309 if (win->prev)
310 win->prev->next = win->next;
311 if (win->next)
312 win->next->prev = win->prev;
313 if (vis->windows == win)
314 vis->windows = win->next;
315 if (vis->win == win)
316 vis->win = win->next ? win->next : win->prev;
317 if (win == vis->message_window)
318 vis->message_window = NULL;
319 window_free(win);
320 if (vis->win)
321 vis->ui->window_focus(vis->win->ui);
322 vis_draw(vis);
325 Vis *vis_new(Ui *ui, VisEvent *event) {
326 if (!ui)
327 return NULL;
328 Vis *vis = calloc(1, sizeof(Vis));
329 if (!vis)
330 return NULL;
331 vis->ui = ui;
332 vis->ui->init(vis->ui, vis);
333 vis->tabwidth = 8;
334 vis->expandtab = false;
335 vis->registers[VIS_REG_BLACKHOLE].type = REGISTER_BLACKHOLE;
336 vis->registers[VIS_REG_CLIPBOARD].type = REGISTER_CLIPBOARD;
337 action_reset(&vis->action);
338 if (!(vis->search_pattern = text_regex_new()))
339 goto err;
340 if (!(vis->command_file = file_new_internal(vis, NULL)))
341 goto err;
342 if (!(vis->search_file = file_new_internal(vis, NULL)))
343 goto err;
344 vis->mode_prev = vis->mode = &vis_modes[VIS_MODE_NORMAL];
345 vis->event = event;
346 if (event && event->vis_start)
347 event->vis_start(vis);
348 vis->ui->start(vis->ui);
349 return vis;
350 err:
351 vis_free(vis);
352 return NULL;
355 void vis_free(Vis *vis) {
356 if (!vis)
357 return;
358 if (vis->event && vis->event->vis_quit)
359 vis->event->vis_quit(vis);
360 vis->event = NULL;
361 while (vis->windows)
362 vis_window_close(vis->windows);
363 file_free(vis, vis->command_file);
364 file_free(vis, vis->search_file);
365 text_regex_free(vis->search_pattern);
366 for (int i = 0; i < LENGTH(vis->registers); i++)
367 register_release(&vis->registers[i]);
368 for (int i = 0; i < LENGTH(vis->macros); i++)
369 macro_release(&vis->macros[i]);
370 vis->ui->free(vis->ui);
371 map_free(vis->cmds);
372 map_free(vis->options);
373 map_free(vis->actions);
374 buffer_release(&vis->input_queue);
375 for (int i = 0; i < VIS_MODE_INVALID; i++)
376 map_free(vis_modes[i].bindings);
377 free(vis);
380 void vis_insert(Vis *vis, size_t pos, const char *data, size_t len) {
381 text_insert(vis->win->file->text, pos, data, len);
382 windows_invalidate(vis, pos, pos + len);
385 void vis_insert_key(Vis *vis, const char *data, size_t len) {
386 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
387 size_t pos = view_cursors_pos(c);
388 vis_insert(vis, pos, data, len);
389 view_cursors_scroll_to(c, pos + len);
393 void vis_replace(Vis *vis, size_t pos, const char *data, size_t len) {
394 Text *txt = vis->win->file->text;
395 Iterator it = text_iterator_get(txt, pos);
396 int chars = text_char_count(data, len);
397 for (char c; chars-- > 0 && text_iterator_byte_get(&it, &c) && c != '\r' && c != '\n'; )
398 text_iterator_char_next(&it, NULL);
400 text_delete(txt, pos, it.pos - pos);
401 vis_insert(vis, pos, data, len);
404 void vis_replace_key(Vis *vis, const char *data, size_t len) {
405 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
406 size_t pos = view_cursors_pos(c);
407 vis_replace(vis, pos, data, len);
408 view_cursors_scroll_to(c, pos + len);
412 void vis_delete(Vis *vis, size_t pos, size_t len) {
413 text_delete(vis->win->file->text, pos, len);
414 windows_invalidate(vis, pos, pos + len);
417 bool vis_action_register(Vis *vis, const KeyAction *action) {
418 if (!vis->actions)
419 vis->actions = map_new();
420 if (!vis->actions)
421 return false;
422 return map_put(vis->actions, action->name, action);
425 static void window_jumplist_add(Win *win, size_t pos) {
426 Mark mark = text_mark_set(win->file->text, pos);
427 if (mark && win->jumplist)
428 ringbuf_add(win->jumplist, mark);
431 static void window_jumplist_invalidate(Win *win) {
432 if (win->jumplist)
433 ringbuf_invalidate(win->jumplist);
436 void action_do(Vis *vis, Action *a) {
437 Win *win = vis->win;
438 Text *txt = win->file->text;
439 View *view = win->view;
441 if (a->op == &vis_operators[VIS_OP_FILTER] && !vis->mode->visual)
442 vis_mode_switch(vis, VIS_MODE_VISUAL_LINE);
444 int count = MAX(a->count, 1);
445 bool repeatable = a->op && !vis->macro_operator;
446 bool multiple_cursors = view_cursors_count(view) > 1;
447 bool linewise = !(a->type & CHARWISE) && (
448 a->type & LINEWISE || (a->movement && a->movement->type & LINEWISE) ||
449 vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE]);
451 for (Cursor *cursor = view_cursors(view), *next; cursor; cursor = next) {
453 next = view_cursors_next(cursor);
454 size_t pos = view_cursors_pos(cursor);
455 Register *reg = multiple_cursors ? view_cursors_register(cursor) : a->reg;
456 if (!reg)
457 reg = &vis->registers[win->file->internal ? VIS_REG_PROMPT : VIS_REG_DEFAULT];
459 OperatorContext c = {
460 .count = count,
461 .pos = pos,
462 .newpos = EPOS,
463 .range = text_range_empty(),
464 .reg = reg,
465 .linewise = linewise,
466 .arg = &a->arg,
469 if (a->movement) {
470 size_t start = pos;
471 for (int i = 0; i < count; i++) {
472 if (a->movement->txt)
473 pos = a->movement->txt(txt, pos);
474 else if (a->movement->cur)
475 pos = a->movement->cur(cursor);
476 else if (a->movement->file)
477 pos = a->movement->file(vis, vis->win->file, pos);
478 else if (a->movement->vis)
479 pos = a->movement->vis(vis, txt, pos);
480 else if (a->movement->view)
481 pos = a->movement->view(vis, view);
482 else if (a->movement->win)
483 pos = a->movement->win(vis, win, pos);
484 if (pos == EPOS || a->movement->type & IDEMPOTENT)
485 break;
488 if (pos == EPOS) {
489 c.range.start = start;
490 c.range.end = start;
491 pos = start;
492 } else {
493 c.range = text_range_new(start, pos);
494 c.newpos = pos;
497 if (!a->op) {
498 if (a->movement->type & CHARWISE)
499 view_cursors_scroll_to(cursor, pos);
500 else
501 view_cursors_to(cursor, pos);
502 if (vis->mode->visual)
503 c.range = view_cursors_selection_get(cursor);
504 if (a->movement->type & JUMP)
505 window_jumplist_add(win, pos);
506 else
507 window_jumplist_invalidate(win);
508 } else if (a->movement->type & INCLUSIVE) {
509 c.range.end = text_char_next(txt, c.range.end);
511 } else if (a->textobj) {
512 if (vis->mode->visual)
513 c.range = view_cursors_selection_get(cursor);
514 else
515 c.range.start = c.range.end = pos;
516 for (int i = 0; i < count; i++) {
517 Filerange r;
518 if (a->textobj->txt)
519 r = a->textobj->txt(txt, pos);
520 else
521 r = a->textobj->vis(vis, txt, pos);
522 if (!text_range_valid(&r))
523 break;
524 if (a->textobj->type & OUTER) {
525 r.start--;
526 r.end++;
529 if (a->textobj->type & SPLIT)
530 c.range = r;
531 else
532 c.range = text_range_union(&c.range, &r);
534 if (i < count - 1)
535 pos = c.range.end + 1;
537 } else if (vis->mode->visual) {
538 c.range = view_cursors_selection_get(cursor);
539 if (!text_range_valid(&c.range))
540 c.range.start = c.range.end = pos;
543 if (linewise && vis->mode != &vis_modes[VIS_MODE_VISUAL])
544 c.range = text_range_linewise(txt, &c.range);
545 if (vis->mode->visual) {
546 view_cursors_selection_set(cursor, &c.range);
547 if (vis->mode == &vis_modes[VIS_MODE_VISUAL] || a->textobj)
548 view_cursors_selection_sync(cursor);
551 if (a->op) {
552 size_t pos = a->op->func(vis, txt, &c);
553 if (pos == EPOS) {
554 view_cursors_dispose(cursor);
555 } else if (pos <= text_size(txt)) {
556 /* moving the cursor will affect the selection.
557 * because we want to be able to later restore
558 * the old selection we update it again before
559 * leaving visual mode.
561 Filerange sel = view_cursors_selection_get(cursor);
562 view_cursors_to(cursor, pos);
563 if (vis->mode->visual) {
564 if (sel.start == EPOS && sel.end == EPOS)
565 sel = c.range;
566 else if (sel.start == EPOS)
567 sel = text_range_new(c.range.start, sel.end);
568 else if (sel.end == EPOS)
569 sel = text_range_new(c.range.start, sel.start);
570 if (vis->mode == &vis_modes[VIS_MODE_VISUAL_LINE])
571 sel = text_range_linewise(txt, &sel);
572 view_cursors_selection_set(cursor, &sel);
578 if (a->op) {
579 /* we do not support visual repeat, still do something resonable */
580 if (vis->mode->visual && !a->movement && !a->textobj)
581 a->movement = &vis_motions[VIS_MOVE_NOP];
583 /* operator implementations must not change the mode,
584 * they might get called multiple times (once for every cursor)
586 if (a->op == &vis_operators[VIS_OP_INSERT] || a->op == &vis_operators[VIS_OP_CHANGE]) {
587 vis_mode_switch(vis, VIS_MODE_INSERT);
588 } else if (a->op == &vis_operators[VIS_OP_REPLACE]) {
589 vis_mode_switch(vis, VIS_MODE_REPLACE);
590 } else if (a->op == &vis_operators[VIS_OP_FILTER]) {
591 if (a->arg.s) {
592 vis_mode_switch(vis, VIS_MODE_NORMAL);
593 vis_cmd(vis, a->arg.s);
594 } else {
595 vis_prompt_show(vis, ":'<,'>!");
597 } else if (vis->mode == &vis_modes[VIS_MODE_OPERATOR_PENDING]) {
598 mode_set(vis, vis->mode_prev);
599 } else if (vis->mode->visual) {
600 vis_mode_switch(vis, VIS_MODE_NORMAL);
602 text_snapshot(txt);
603 vis_draw(vis);
606 if (a != &vis->action_prev) {
607 if (repeatable) {
608 if (!a->macro)
609 a->macro = vis->macro_operator;
610 vis->action_prev = *a;
612 action_reset(a);
616 void action_reset(Action *a) {
617 memset(a, 0, sizeof(*a));
618 a->count = VIS_COUNT_UNKNOWN;
621 void vis_cancel(Vis *vis) {
622 action_reset(&vis->action);
625 void vis_die(Vis *vis, const char *msg, ...) {
626 va_list ap;
627 va_start(ap, msg);
628 vis->ui->die(vis->ui, msg, ap);
629 va_end(ap);
632 const char *vis_keys_next(Vis *vis, const char *keys) {
633 TermKeyKey key;
634 TermKey *termkey = vis->ui->termkey_get(vis->ui);
635 const char *next = NULL;
636 if (!keys)
637 return NULL;
638 /* first try to parse a special key of the form <Key> */
639 if (*keys == '<' && (next = termkey_strpkey(termkey, keys+1, &key, TERMKEY_FORMAT_VIM)) && *next == '>')
640 return next+1;
641 if (*keys == '<') {
642 const char *start = keys + 1, *end = start;
643 while (*end && *end != '>')
644 end++;
645 if (end > start && end - start - 1 < 64 && *end == '>') {
646 char key[64];
647 memcpy(key, start, end - start);
648 key[end - start] = '\0';
649 if (map_get(vis->actions, key))
650 return end + 1;
653 while (!ISUTF8(*keys))
654 keys++;
655 return termkey_strpkey(termkey, keys, &key, TERMKEY_FORMAT_VIM);
658 static const char *vis_keys_raw(Vis *vis, Buffer *buf, const char *input) {
659 char *keys = buf->data, *start = keys, *cur = keys, *end;
660 bool prefix = false;
661 KeyBinding *binding = NULL;
663 while (cur && *cur) {
665 if (!(end = (char*)vis_keys_next(vis, cur)))
666 goto err; // XXX: can't parse key this should never happen
668 char tmp = *end;
669 *end = '\0';
670 prefix = false;
671 binding = NULL;
673 for (Mode *mode = vis->mode; mode && !binding && !prefix; mode = mode->parent) {
674 for (int global = 0; global < 2 && !binding && !prefix; global++) {
675 Mode *mode_local = global ? mode : &vis->win->modes[mode->id];
676 if (!mode_local->bindings)
677 continue;
678 binding = map_get(mode_local->bindings, start);
679 /* "<" is never treated as a prefix because it is used to denote
680 * special key symbols */
681 if (strcmp(cur, "<"))
682 prefix = !binding && map_contains(mode_local->bindings, start);
686 *end = tmp;
687 vis->keys = buf;
689 if (binding) { /* exact match */
690 if (binding->action) {
691 end = (char*)binding->action->func(vis, end, &binding->action->arg);
692 if (!end)
693 break;
694 start = cur = end;
695 } else if (binding->alias) {
696 buffer_put0(buf, end);
697 buffer_prepend0(buf, binding->alias);
698 start = cur = buf->data;
700 } else if (prefix) { /* incomplete key binding? */
701 cur = end;
702 } else { /* no keybinding */
703 KeyAction *action = NULL;
704 if (start[0] == '<' && end[-1] == '>') {
705 /* test for special editor key command */
706 char tmp = end[-1];
707 end[-1] = '\0';
708 action = map_get(vis->actions, start+1);
709 end[-1] = tmp;
710 if (action) {
711 end = (char*)action->func(vis, end, &action->arg);
712 if (!end)
713 break;
716 if (!action && vis->mode->input)
717 vis->mode->input(vis, start, end - start);
718 start = cur = end;
722 vis->keys = NULL;
723 buffer_put0(buf, start);
724 return input + (start - keys);
725 err:
726 vis->keys = NULL;
727 buffer_truncate(buf);
728 return input + strlen(input);
731 bool vis_keys_inject(Vis *vis, const char *pos, const char *input) {
732 Buffer *buf = vis->keys;
733 if (!buf)
734 return false;
735 if (pos < buf->data || pos > buf->data + buf->len)
736 return false;
737 size_t off = pos - buf->data;
738 buffer_insert0(buf, off, input);
739 if (vis->macro_operator)
740 macro_append(vis->macro_operator, input);
741 return true;
744 const char *vis_keys_push(Vis *vis, const char *input) {
745 if (!input)
746 return NULL;
748 if (vis->recording)
749 macro_append(vis->recording, input);
750 if (vis->macro_operator)
751 macro_append(vis->macro_operator, input);
753 if (!buffer_append0(&vis->input_queue, input)) {
754 buffer_truncate(&vis->input_queue);
755 return NULL;
758 return vis_keys_raw(vis, &vis->input_queue, input);
761 static const char *getkey(Vis *vis) {
762 const char *key = vis->ui->getkey(vis->ui);
763 if (!key)
764 return NULL;
765 vis_info_hide(vis);
766 return key;
769 bool vis_signal_handler(Vis *vis, int signum, const siginfo_t *siginfo, const void *context) {
770 switch (signum) {
771 case SIGBUS:
772 for (File *file = vis->files; file; file = file->next) {
773 if (text_sigbus(file->text, siginfo->si_addr))
774 file->truncated = true;
776 vis->sigbus = true;
777 if (vis->running)
778 siglongjmp(vis->sigbus_jmpbuf, 1);
779 return true;
780 case SIGINT:
781 vis->cancel_filter = true;
782 return true;
784 return false;
787 static void vis_args(Vis *vis, int argc, char *argv[]) {
788 char *cmd = NULL;
789 bool end_of_options = false;
790 for (int i = 1; i < argc; i++) {
791 if (argv[i][0] == '-' && !end_of_options) {
792 switch (argv[i][1]) {
793 case '-':
794 end_of_options = true;
795 break;
796 case 'v':
797 vis_die(vis, "vis %s, compiled " __DATE__ " " __TIME__ "\n", VERSION);
798 break;
799 case '\0':
800 break;
801 default:
802 vis_die(vis, "Unknown command option: %s\n", argv[i]);
803 break;
805 } else if (argv[i][0] == '+') {
806 cmd = argv[i] + (argv[i][1] == '/' || argv[i][1] == '?');
807 } else if (!vis_window_new(vis, argv[i])) {
808 vis_die(vis, "Can not load `%s': %s\n", argv[i], strerror(errno));
809 } else if (cmd) {
810 vis_prompt_cmd(vis, cmd);
811 cmd = NULL;
815 if (!vis->windows) {
816 if (!strcmp(argv[argc-1], "-")) {
817 if (!vis_window_new(vis, NULL))
818 vis_die(vis, "Can not create empty buffer\n");
819 ssize_t len = 0;
820 char buf[PIPE_BUF];
821 File *file = vis->win->file;
822 Text *txt = file->text;
823 file->is_stdin = true;
824 while ((len = read(STDIN_FILENO, buf, sizeof buf)) > 0)
825 text_insert(txt, text_size(txt), buf, len);
826 if (len == -1)
827 vis_die(vis, "Can not read from stdin\n");
828 text_snapshot(txt);
829 int fd = open("/dev/tty", O_RDONLY);
830 if (fd == -1)
831 vis_die(vis, "Can not reopen stdin\n");
832 dup2(fd, STDIN_FILENO);
833 close(fd);
834 } else if (!vis_window_new(vis, NULL)) {
835 vis_die(vis, "Can not create empty buffer\n");
837 if (cmd)
838 vis_prompt_cmd(vis, cmd);
842 int vis_run(Vis *vis, int argc, char *argv[]) {
843 vis_args(vis, argc, argv);
845 struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL;
847 sigset_t emptyset;
848 sigemptyset(&emptyset);
849 vis_draw(vis);
850 vis->running = true;
851 vis->exit_status = EXIT_SUCCESS;
853 sigsetjmp(vis->sigbus_jmpbuf, 1);
855 while (vis->running) {
856 fd_set fds;
857 FD_ZERO(&fds);
858 FD_SET(STDIN_FILENO, &fds);
860 if (vis->sigbus) {
861 char *name = NULL;
862 for (Win *next, *win = vis->windows; win; win = next) {
863 next = win->next;
864 if (win->file->truncated) {
865 free(name);
866 name = strdup(win->file->name);
867 vis_window_close(win);
870 if (!vis->windows)
871 vis_die(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
872 else
873 vis_info_show(vis, "WARNING: file `%s' truncated!\n", name ? name : "-");
874 vis->sigbus = false;
875 free(name);
878 vis_update(vis);
879 idle.tv_sec = vis->mode->idle_timeout;
880 int r = pselect(1, &fds, NULL, NULL, timeout, &emptyset);
881 if (r == -1 && errno == EINTR)
882 continue;
884 if (r < 0) {
885 /* TODO save all pending changes to a ~suffixed file */
886 vis_die(vis, "Error in mainloop: %s\n", strerror(errno));
889 if (!FD_ISSET(STDIN_FILENO, &fds)) {
890 if (vis->mode->idle)
891 vis->mode->idle(vis);
892 timeout = NULL;
893 continue;
896 TermKey *termkey = vis->ui->termkey_get(vis->ui);
897 termkey_advisereadable(termkey);
898 const char *key;
900 while ((key = getkey(vis)))
901 vis_keys_push(vis, key);
903 if (vis->mode->idle)
904 timeout = &idle;
906 return vis->exit_status;
909 static Macro *macro_get(Vis *vis, enum VisMacro m) {
910 if (m == VIS_MACRO_LAST_RECORDED)
911 return vis->last_recording;
912 if (m < LENGTH(vis->macros))
913 return &vis->macros[m];
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 VisMacro id) {
927 Macro *macro = macro_get(vis, id);
928 if (vis->recording || !macro)
929 return false;
930 macro_reset(macro);
931 vis->recording = macro;
932 return true;
935 bool vis_macro_record_stop(Vis *vis) {
936 if (!vis->recording)
937 return false;
938 /* XXX: hack to remove last recorded key, otherwise upon replay
939 * we would start another recording */
940 if (vis->recording->len > 1) {
941 vis->recording->len--;
942 vis->recording->data[vis->recording->len-1] = '\0';
944 vis->last_recording = vis->recording;
945 vis->recording = NULL;
946 return true;
949 bool vis_macro_recording(Vis *vis) {
950 return vis->recording;
953 static void macro_replay(Vis *vis, const Macro *macro) {
954 Buffer buf;
955 buffer_init(&buf);
956 buffer_put(&buf, macro->data, macro->len);
957 vis_keys_raw(vis, &buf, macro->data);
958 buffer_release(&buf);
961 bool vis_macro_replay(Vis *vis, enum VisMacro id) {
962 Macro *macro = macro_get(vis, id);
963 if (!macro || macro == vis->recording)
964 return false;
965 macro_replay(vis, macro);
966 return true;
969 void vis_repeat(Vis *vis) {
970 int count = vis->action.count;
971 Macro *macro_operator = macro_get(vis, VIS_MACRO_OPERATOR);
972 Macro *macro_repeat = macro_get(vis, VIS_MACRO_REPEAT);
973 const Macro *macro = vis->action_prev.macro;
974 if (macro == macro_operator) {
975 buffer_put(macro_repeat, macro_operator->data, macro_operator->len);
976 macro = macro_repeat;
977 vis->action_prev.macro = macro;
979 if (count != VIS_COUNT_UNKNOWN)
980 vis->action_prev.count = count;
981 count = vis->action_prev.count;
982 /* for some operators count should be applied only to the macro not the motion */
983 if (vis->action_prev.op == &vis_operators[VIS_OP_INSERT] || vis->action_prev.op == &vis_operators[VIS_OP_REPLACE])
984 vis->action_prev.count = 1;
985 action_do(vis, &vis->action_prev);
986 vis->action_prev.count = count;
987 if (macro) {
988 Mode *mode = vis->mode;
989 Action action_prev = vis->action_prev;
990 count = action_prev.count;
991 if (count < 1 || action_prev.op == &vis_operators[VIS_OP_CHANGE] || action_prev.op == &vis_operators[VIS_OP_FILTER])
992 count = 1;
993 for (int i = 0; i < count; i++) {
994 mode_set(vis, mode);
995 macro_replay(vis, macro);
997 vis->action_prev = action_prev;
999 vis_cancel(vis);
1002 void vis_mark_set(Vis *vis, enum VisMark mark, size_t pos) {
1003 File *file = vis->win->file;
1004 if (mark < LENGTH(file->marks))
1005 file->marks[mark] = text_mark_set(file->text, pos);
1008 int vis_count_get(Vis *vis) {
1009 return vis->action.count;
1012 int vis_count_get_default(Vis *vis, int def) {
1013 if (vis->action.count == VIS_COUNT_UNKNOWN)
1014 return def;
1015 return vis->action.count;
1018 void vis_count_set(Vis *vis, int count) {
1019 vis->action.count = (count >= 0 ? count : VIS_COUNT_UNKNOWN);
1022 void vis_register_set(Vis *vis, enum VisRegister reg) {
1023 if (reg >= VIS_REG_A && reg <= VIS_REG_Z) {
1024 vis->action.reg = &vis->registers[reg - VIS_REG_A];
1025 vis->action.reg->append = true;
1026 } else if (reg < LENGTH(vis->registers)) {
1027 vis->action.reg = &vis->registers[reg];
1028 vis->action.reg->append = false;
1032 const char *vis_register_get(Vis *vis, enum VisRegister reg, size_t *len) {
1033 if (reg >= VIS_REG_A && reg <= VIS_REG_Z)
1034 reg -= VIS_REG_A;
1035 if (reg < LENGTH(vis->registers))
1036 return register_get(vis, &vis->registers[reg], len);
1037 *len = 0;
1038 return NULL;
1041 void vis_exit(Vis *vis, int status) {
1042 vis->running = false;
1043 vis->exit_status = status;
1046 const char *vis_mode_status(Vis *vis) {
1047 return vis->mode->status;
1050 void vis_insert_tab(Vis *vis) {
1051 if (!vis->expandtab) {
1052 vis_insert_key(vis, "\t", 1);
1053 return;
1055 char spaces[9];
1056 int tabwidth = MIN(vis->tabwidth, LENGTH(spaces) - 1);
1057 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
1058 size_t pos = view_cursors_pos(c);
1059 int width = text_line_width_get(vis->win->file->text, pos);
1060 int count = tabwidth - (width % tabwidth);
1061 for (int i = 0; i < count; i++)
1062 spaces[i] = ' ';
1063 spaces[count] = '\0';
1064 vis_insert(vis, pos, spaces, count);
1065 view_cursors_scroll_to(c, pos + count);
1069 static void copy_indent_from_previous_line(Win *win) {
1070 View *view = win->view;
1071 Text *text = win->file->text;
1072 size_t pos = view_cursor_get(view);
1073 size_t prev_line = text_line_prev(text, pos);
1074 if (pos == prev_line)
1075 return;
1076 size_t begin = text_line_begin(text, prev_line);
1077 size_t start = text_line_start(text, begin);
1078 size_t len = start-begin;
1079 char *buf = malloc(len);
1080 if (!buf)
1081 return;
1082 len = text_bytes_get(text, begin, len, buf);
1083 vis_insert_key(win->vis, buf, len);
1084 free(buf);
1087 void vis_insert_nl(Vis *vis) {
1088 const char *nl;
1089 switch (text_newline_type(vis->win->file->text)) {
1090 case TEXT_NEWLINE_CRNL:
1091 nl = "\r\n";
1092 break;
1093 default:
1094 nl = "\n";
1095 break;
1098 vis_insert_key(vis, nl, strlen(nl));
1100 if (vis->autoindent)
1101 copy_indent_from_previous_line(vis->win);
1104 Text *vis_text(Vis *vis) {
1105 return vis->win->file->text;
1108 View *vis_view(Vis *vis) {
1109 return vis->win->view;
1112 Win *vis_window(Vis *vis) {
1113 return vis->win;
1116 Text *vis_file_text(File *file) {
1117 return file->text;
1120 const char *vis_file_name(File *file) {
1121 return file->name;