13 #include <sys/select.h>
14 #include <sys/types.h>
17 #include <sys/ioctl.h>
24 #include "text-util.h"
25 #include "text-motions.h"
26 #include "text-objects.h"
31 const MarkDef vis_marks
[] = {
32 [VIS_MARK_SELECTION_START
] = { '<', VIS_HELP("Last selection start") },
33 [VIS_MARK_SELECTION_END
] = { '>', VIS_HELP("Last selection end") },
36 const RegisterDef vis_registers
[] = {
37 [VIS_REG_DEFAULT
] = { '"', VIS_HELP("Unnamed register") },
38 [VIS_REG_ZERO
] = { '0', VIS_HELP("Yank register") },
39 [VIS_REG_1
] = { '1', VIS_HELP("1st sub-expression match") },
40 [VIS_REG_2
] = { '2', VIS_HELP("2nd sub-expression match") },
41 [VIS_REG_3
] = { '3', VIS_HELP("3rd sub-expression match") },
42 [VIS_REG_4
] = { '4', VIS_HELP("4th sub-expression match") },
43 [VIS_REG_5
] = { '5', VIS_HELP("5th sub-expression match") },
44 [VIS_REG_6
] = { '6', VIS_HELP("6th sub-expression match") },
45 [VIS_REG_7
] = { '7', VIS_HELP("7th sub-expression match") },
46 [VIS_REG_8
] = { '8', VIS_HELP("8th sub-expression match") },
47 [VIS_REG_9
] = { '9', VIS_HELP("9th sub-expression match") },
48 [VIS_REG_AMPERSAND
] = { '&', VIS_HELP("Last regex match") },
49 [VIS_REG_BLACKHOLE
] = { '_', VIS_HELP("/dev/null register") },
50 [VIS_REG_CLIPBOARD
] = { '*', VIS_HELP("System clipboard register, see vis-clipboard(1)") },
51 [VIS_REG_DOT
] = { '.', VIS_HELP("Last inserted text") },
52 [VIS_REG_SEARCH
] = { '/', VIS_HELP("Last search pattern") },
53 [VIS_REG_COMMAND
] = { ':', VIS_HELP("Last :-command") },
54 [VIS_REG_SHELL
] = { '!', VIS_HELP("Last shell command given to either <, >, |, or !") },
57 static Macro
*macro_get(Vis
*vis
, enum VisRegister
);
58 static void macro_replay(Vis
*vis
, const Macro
*macro
);
59 static void macro_replay_internal(Vis
*vis
, const Macro
*macro
);
60 static void vis_keys_push(Vis
*vis
, const char *input
, size_t pos
, bool record
);
62 bool vis_event_emit(Vis
*vis
, enum VisEvents id
, ...) {
66 if (!vis
->initialized
) {
67 vis
->initialized
= true;
68 vis
->ui
->init(vis
->ui
, vis
);
70 vis
->event
->init(vis
);
81 if (vis
->event
->start
)
82 vis
->event
->start(vis
);
84 case VIS_EVENT_FILE_OPEN
:
85 case VIS_EVENT_FILE_SAVE_PRE
:
86 case VIS_EVENT_FILE_SAVE_POST
:
87 case VIS_EVENT_FILE_CLOSE
:
89 File
*file
= va_arg(ap
, File
*);
92 if (id
== VIS_EVENT_FILE_OPEN
&& vis
->event
->file_open
) {
93 vis
->event
->file_open(vis
, file
);
94 } else if (id
== VIS_EVENT_FILE_SAVE_PRE
&& vis
->event
->file_save_pre
) {
95 const char *path
= va_arg(ap
, const char*);
96 ret
= vis
->event
->file_save_pre(vis
, file
, path
);
97 } else if (id
== VIS_EVENT_FILE_SAVE_POST
&& vis
->event
->file_save_post
) {
98 const char *path
= va_arg(ap
, const char*);
99 vis
->event
->file_save_post(vis
, file
, path
);
100 } else if (id
== VIS_EVENT_FILE_CLOSE
&& vis
->event
->file_close
) {
101 vis
->event
->file_close(vis
, file
);
105 case VIS_EVENT_WIN_OPEN
:
106 case VIS_EVENT_WIN_CLOSE
:
107 case VIS_EVENT_WIN_HIGHLIGHT
:
108 case VIS_EVENT_WIN_SYNTAX
:
109 case VIS_EVENT_WIN_STATUS
:
111 Win
*win
= va_arg(ap
, Win
*);
112 if (win
->file
->internal
)
114 if (vis
->event
->win_open
&& id
== VIS_EVENT_WIN_OPEN
) {
115 vis
->event
->win_open(vis
, win
);
116 } else if (vis
->event
->win_close
&& id
== VIS_EVENT_WIN_CLOSE
) {
117 vis
->event
->win_close(vis
, win
);
118 } else if (vis
->event
->win_highlight
&& id
== VIS_EVENT_WIN_HIGHLIGHT
) {
119 vis
->event
->win_highlight(vis
, win
, win
->horizon
);
120 } else if (vis
->event
->win_syntax
&& id
== VIS_EVENT_WIN_SYNTAX
) {
121 const char *syntax
= va_arg(ap
, const char*);
122 ret
= vis
->event
->win_syntax(vis
, win
, syntax
);
123 } else if (vis
->event
->win_status
&& id
== VIS_EVENT_WIN_STATUS
) {
124 vis
->event
->win_status(vis
, win
);
129 if (vis
->event
->quit
)
130 vis
->event
->quit(vis
);
138 /** window / file handling */
140 static void file_free(Vis
*vis
, File
*file
) {
143 if (file
->refcount
> 1) {
147 vis_event_emit(vis
, VIS_EVENT_FILE_CLOSE
, file
);
148 text_free(file
->text
);
149 free((char*)file
->name
);
152 file
->prev
->next
= file
->next
;
154 file
->next
->prev
= file
->prev
;
155 if (vis
->files
== file
)
156 vis
->files
= file
->next
;
160 static File
*file_new_text(Vis
*vis
, Text
*text
) {
161 File
*file
= calloc(1, sizeof(*file
));
166 file
->stat
= text_stat(text
);
168 vis
->files
->prev
= file
;
169 file
->next
= vis
->files
;
174 static char *absolute_path(const char *name
) {
177 char *copy1
= strdup(name
);
178 char *copy2
= strdup(name
);
179 char *path_absolute
= NULL
;
180 char path_normalized
[PATH_MAX
] = "";
182 if (!copy1
|| !copy2
)
185 char *dir
= dirname(copy1
);
186 char *base
= basename(copy2
);
187 if (!(path_absolute
= realpath(dir
, NULL
)))
190 snprintf(path_normalized
, sizeof(path_normalized
)-1, "%s/%s",
191 path_absolute
, base
);
196 return path_normalized
[0] ? strdup(path_normalized
) : NULL
;
199 static File
*file_new(Vis
*vis
, const char *name
) {
200 char *name_absolute
= NULL
;
202 if (!(name_absolute
= absolute_path(name
)))
204 File
*existing
= NULL
;
205 /* try to detect whether the same file is already open in another window
206 * TODO: do this based on inodes */
207 for (File
*file
= vis
->files
; file
; file
= file
->next
) {
208 if (file
->name
&& strcmp(file
->name
, name_absolute
) == 0) {
220 Text
*text
= text_load(name
);
221 if (!text
&& name
&& errno
== ENOENT
)
222 text
= text_load(NULL
);
225 if (!(file
= file_new_text(vis
, text
)))
227 file
->name
= name_absolute
;
228 vis_event_emit(vis
, VIS_EVENT_FILE_OPEN
, file
);
233 file_free(vis
, file
);
237 static File
*file_new_internal(Vis
*vis
, const char *filename
) {
238 File
*file
= file_new(vis
, filename
);
241 file
->internal
= true;
246 void file_name_set(File
*file
, const char *name
) {
247 if (name
== file
->name
)
249 free((char*)file
->name
);
250 file
->name
= absolute_path(name
);
253 const char *file_name_get(File
*file
) {
254 /* TODO: calculate path relative to working directory, cache result */
258 if (!getcwd(cwd
, sizeof cwd
))
260 const char *path
= strstr(file
->name
, cwd
);
261 if (path
!= file
->name
)
263 size_t cwdlen
= strlen(cwd
);
264 return file
->name
[cwdlen
] == '/' ? file
->name
+cwdlen
+1 : file
->name
;
267 void vis_window_status(Win
*win
, const char *status
) {
268 win
->ui
->status(win
->ui
, status
);
271 static void windows_invalidate(Vis
*vis
, size_t start
, size_t end
) {
272 for (Win
*win
= vis
->windows
; win
; win
= win
->next
) {
273 if (vis
->win
->file
== win
->file
) {
274 Filerange view
= view_viewport_get(win
->view
);
275 if ((view
.start
<= start
&& start
<= view
.end
) ||
276 (view
.start
<= end
&& end
<= view
.end
))
277 view_draw(win
->view
);
282 void window_selection_save(Win
*win
) {
283 File
*file
= win
->file
;
284 Filerange sel
= view_cursors_selection_get(view_cursors(win
->view
));
285 file
->marks
[VIS_MARK_SELECTION_START
] = text_mark_set(file
->text
, sel
.start
);
286 file
->marks
[VIS_MARK_SELECTION_END
] = text_mark_set(file
->text
, sel
.end
);
289 static void window_free(Win
*win
) {
293 for (Win
*other
= vis
->windows
; other
; other
= other
->next
) {
294 if (other
->parent
== win
)
295 other
->parent
= NULL
;
298 vis
->ui
->window_free(win
->ui
);
299 view_free(win
->view
);
300 for (size_t i
= 0; i
< LENGTH(win
->modes
); i
++)
301 map_free(win
->modes
[i
].bindings
);
302 ringbuf_free(win
->jumplist
);
303 free(win
->lexer_name
);
307 static void window_draw_colorcolumn(Win
*win
) {
308 View
*view
= win
->view
;
309 int cc
= view_colorcolumn_get(view
);
312 CellStyle style
= win
->ui
->style_get(win
->ui
, UI_STYLE_COLOR_COLUMN
);
314 int line_cols
= 0; /* Track the number of columns we've passed on each line */
315 bool line_cc_set
= false; /* Has the colorcolumn attribute been set for this line yet */
316 int width
= view_width_get(view
);
318 for (Line
*l
= view_lines_first(view
); l
; l
= l
->next
) {
319 if (l
->lineno
!= lineno
) {
329 /* This screen line contains the cell we want to highlight */
330 if (line_cols
>= cc
) {
331 l
->cells
[(cc
- 1) % width
].style
= style
;
337 static void window_draw_cursorline(Win
*win
) {
339 View
*view
= win
->view
;
340 enum UiOption options
= view_options_get(view
);
341 if (!(options
& UI_OPTION_CURSOR_LINE
))
343 if (vis
->mode
->visual
|| vis
->win
!= win
)
345 if (view_cursors_multiple(view
))
348 int width
= view_width_get(view
);
349 CellStyle style
= win
->ui
->style_get(win
->ui
, UI_STYLE_CURSOR_LINE
);
350 Cursor
*cursor
= view_cursors_primary_get(view
);
351 size_t lineno
= view_cursors_line_get(cursor
)->lineno
;
352 for (Line
*l
= view_lines_first(view
); l
; l
= l
->next
) {
353 if (l
->lineno
== lineno
) {
354 for (int x
= 0; x
< width
; x
++) {
355 l
->cells
[x
].style
.attr
|= style
.attr
;
356 l
->cells
[x
].style
.bg
= style
.bg
;
358 } else if (l
->lineno
> lineno
) {
364 static void window_draw_selection(View
*view
, Cursor
*cur
, CellStyle
*style
) {
365 Filerange sel
= view_cursors_selection_get(cur
);
366 if (!text_range_valid(&sel
))
368 Line
*start_line
; int start_col
;
369 Line
*end_line
; int end_col
;
370 view_coord_get(view
, sel
.start
, &start_line
, NULL
, &start_col
);
371 view_coord_get(view
, sel
.end
, &end_line
, NULL
, &end_col
);
372 if (!start_line
&& !end_line
)
375 start_line
= view_lines_first(view
);
379 end_line
= view_lines_last(view
);
380 end_col
= end_line
->width
;
382 for (Line
*l
= start_line
; l
!= end_line
->next
; l
= l
->next
) {
383 int col
= (l
== start_line
) ? start_col
: 0;
384 int end
= (l
== end_line
) ? end_col
: l
->width
;
386 if (cell_color_equal(l
->cells
[col
].style
.fg
, style
->fg
)) {
387 CellStyle old
= l
->cells
[col
].style
;
388 l
->cells
[col
].style
.fg
= old
.bg
;
389 l
->cells
[col
].style
.bg
= old
.fg
;
391 l
->cells
[col
].style
.bg
= style
->bg
;
398 static void window_draw_cursor_matching(Win
*win
, Cursor
*cur
, CellStyle
*style
) {
399 if (win
->vis
->mode
->visual
)
401 Line
*line_match
; int col_match
;
402 size_t pos
= view_cursors_pos(cur
);
403 size_t pos_match
= text_bracket_match_symbol(win
->file
->text
, pos
, "(){}[]\"'`");
404 if (pos
== pos_match
)
406 if (!view_coord_get(win
->view
, pos_match
, &line_match
, NULL
, &col_match
))
408 if (cell_color_equal(line_match
->cells
[col_match
].style
.fg
, style
->fg
)) {
409 CellStyle old
= line_match
->cells
[col_match
].style
;
410 line_match
->cells
[col_match
].style
.fg
= old
.bg
;
411 line_match
->cells
[col_match
].style
.bg
= old
.fg
;
413 line_match
->cells
[col_match
].style
.bg
= style
->bg
;
417 static void window_draw_cursor(Win
*win
, Cursor
*cur
, CellStyle
*style
, CellStyle
*sel_style
) {
418 if (win
->vis
->win
!= win
)
420 Line
*line
= view_cursors_line_get(cur
);
421 int col
= view_cursors_cell_get(cur
);
422 if (!line
|| col
== -1)
424 line
->cells
[col
].style
= *style
;
425 window_draw_cursor_matching(win
, cur
, sel_style
);
429 static void window_draw_cursors(Win
*win
) {
430 View
*view
= win
->view
;
431 Filerange viewport
= view_viewport_get(view
);
432 bool multiple_cursors
= view_cursors_multiple(view
);
433 Cursor
*cursor
= view_cursors_primary_get(view
);
434 CellStyle style_cursor
= win
->ui
->style_get(win
->ui
, UI_STYLE_CURSOR
);
435 CellStyle style_cursor_primary
= win
->ui
->style_get(win
->ui
, UI_STYLE_CURSOR_PRIMARY
);
436 CellStyle style_selection
= win
->ui
->style_get(win
->ui
, UI_STYLE_SELECTION
);
437 for (Cursor
*c
= view_cursors_prev(cursor
); c
; c
= view_cursors_prev(c
)) {
438 window_draw_selection(win
->view
, c
, &style_selection
);
439 size_t pos
= view_cursors_pos(c
);
440 if (pos
< viewport
.start
)
442 window_draw_cursor(win
, c
, &style_cursor
, &style_selection
);
444 window_draw_selection(win
->view
, cursor
, &style_selection
);
445 window_draw_cursor(win
, cursor
, multiple_cursors
? &style_cursor_primary
: &style_cursor
, &style_selection
);
446 for (Cursor
*c
= view_cursors_next(cursor
); c
; c
= view_cursors_next(c
)) {
447 window_draw_selection(win
->view
, c
, &style_selection
);
448 size_t pos
= view_cursors_pos(c
);
449 if (pos
> viewport
.end
)
451 window_draw_cursor(win
, c
, &style_cursor
, &style_selection
);
455 static void window_draw_eof(Win
*win
) {
456 View
*view
= win
->view
;
457 if (view_width_get(view
) == 0)
459 CellStyle style
= win
->ui
->style_get(win
->ui
, UI_STYLE_EOF
);
460 for (Line
*l
= view_lines_last(view
)->next
; l
; l
= l
->next
) {
461 strcpy(l
->cells
[0].data
, "~");
462 l
->cells
[0].style
= style
;
466 void vis_window_draw(Win
*win
) {
467 if (!win
->ui
|| !view_update(win
->view
))
470 vis_event_emit(vis
, VIS_EVENT_WIN_HIGHLIGHT
, win
);
472 window_draw_colorcolumn(win
);
473 window_draw_cursorline(win
);
474 window_draw_cursors(win
);
475 window_draw_eof(win
);
477 vis_event_emit(vis
, VIS_EVENT_WIN_STATUS
, win
);
480 Win
*window_new_file(Vis
*vis
, File
*file
, enum UiOption options
) {
481 Win
*win
= calloc(1, sizeof(Win
));
486 win
->jumplist
= ringbuf_alloc(31);
487 win
->horizon
= 1 << 15;
488 win
->view
= view_new(file
->text
);
489 win
->ui
= vis
->ui
->window_new(vis
->ui
, win
, options
);
490 if (!win
->jumplist
|| !win
->view
|| !win
->ui
) {
495 view_tabwidth_set(win
->view
, vis
->tabwidth
);
498 vis
->windows
->prev
= win
;
499 win
->next
= vis
->windows
;
502 vis
->ui
->window_focus(win
->ui
);
503 for (size_t i
= 0; i
< LENGTH(win
->modes
); i
++)
504 win
->modes
[i
].parent
= &vis_modes
[i
];
505 vis_event_emit(vis
, VIS_EVENT_WIN_OPEN
, win
);
509 bool vis_window_reload(Win
*win
) {
510 const char *name
= win
->file
->name
;
512 return false; /* can't reload unsaved file */
513 /* temporarily unset file name, otherwise file_new returns the same File */
514 win
->file
->name
= NULL
;
515 File
*file
= file_new(win
->vis
, name
);
516 win
->file
->name
= name
;
519 file_free(win
->vis
, win
->file
);
522 view_reload(win
->view
, file
->text
);
526 bool vis_window_split(Win
*original
) {
527 Win
*win
= window_new_file(original
->vis
, original
->file
, UI_OPTION_STATUSBAR
);
530 for (size_t i
= 0; i
< LENGTH(win
->modes
); i
++) {
531 if (original
->modes
[i
].bindings
)
532 win
->modes
[i
].bindings
= map_new();
533 if (win
->modes
[i
].bindings
)
534 map_copy(win
->modes
[i
].bindings
, original
->modes
[i
].bindings
);
536 win
->file
= original
->file
;
537 vis_window_syntax_set(win
, vis_window_syntax_get(original
));
538 view_options_set(win
->view
, view_options_get(original
->view
));
539 view_cursor_to(win
->view
, view_cursor_get(original
->view
));
543 void vis_window_focus(Win
*win
) {
548 vis
->ui
->window_focus(win
->ui
);
551 void vis_window_next(Vis
*vis
) {
555 vis_window_focus(sel
->next
? sel
->next
: vis
->windows
);
558 void vis_window_prev(Vis
*vis
) {
564 for (sel
= vis
->windows
; sel
->next
; sel
= sel
->next
);
565 vis_window_focus(sel
);
568 const char *vis_window_syntax_get(Win
*win
) {
569 return win
->lexer_name
;
572 bool vis_window_syntax_set(Win
*win
, const char *syntax
) {
573 if (!vis_event_emit(win
->vis
, VIS_EVENT_WIN_SYNTAX
, win
, syntax
))
575 view_options_set(win
->view
, view_options_get(win
->view
));
576 free(win
->lexer_name
);
577 win
->lexer_name
= syntax
? strdup(syntax
) : NULL
;
578 return !syntax
|| win
->lexer_name
;
581 int vis_window_width_get(const Win
*win
) {
582 return win
->ui
->window_width(win
->ui
);
585 int vis_window_height_get(const Win
*win
) {
586 return win
->ui
->window_height(win
->ui
);
589 void vis_draw(Vis
*vis
) {
590 for (Win
*win
= vis
->windows
; win
; win
= win
->next
)
591 view_draw(win
->view
);
594 void vis_redraw(Vis
*vis
) {
595 vis
->ui
->redraw(vis
->ui
);
598 void vis_update(Vis
*vis
) {
599 vis
->ui
->draw(vis
->ui
);
602 void vis_suspend(Vis
*vis
) {
603 vis
->ui
->suspend(vis
->ui
);
606 bool vis_window_new(Vis
*vis
, const char *filename
) {
607 File
*file
= file_new(vis
, filename
);
610 Win
*win
= window_new_file(vis
, file
, UI_OPTION_STATUSBAR
);
612 file_free(vis
, file
);
619 bool vis_window_new_fd(Vis
*vis
, int fd
) {
622 if (!vis_window_new(vis
, NULL
))
624 vis
->win
->file
->fd
= fd
;
628 bool vis_window_closable(Win
*win
) {
629 if (!win
|| !text_modified(win
->file
->text
))
631 return win
->file
->refcount
> 1;
634 void vis_window_swap(Win
*a
, Win
*b
) {
635 if (a
== b
|| !a
|| !b
)
652 if (vis
->windows
== a
)
654 else if (vis
->windows
== b
)
656 vis
->ui
->window_swap(a
->ui
, b
->ui
);
659 else if (vis
->win
== b
)
663 void vis_window_close(Win
*win
) {
667 vis_event_emit(vis
, VIS_EVENT_WIN_CLOSE
, win
);
668 file_free(vis
, win
->file
);
670 win
->prev
->next
= win
->next
;
672 win
->next
->prev
= win
->prev
;
673 if (vis
->windows
== win
)
674 vis
->windows
= win
->next
;
676 vis
->win
= win
->next
? win
->next
: win
->prev
;
677 if (win
== vis
->message_window
)
678 vis
->message_window
= NULL
;
681 vis
->ui
->window_focus(vis
->win
->ui
);
685 Vis
*vis_new(Ui
*ui
, VisEvent
*event
) {
688 Vis
*vis
= calloc(1, sizeof(Vis
));
691 vis
->exit_status
= -1;
694 vis
->expandtab
= false;
695 vis
->change_colors
= true;
696 vis
->registers
[VIS_REG_BLACKHOLE
].type
= REGISTER_BLACKHOLE
;
697 vis
->registers
[VIS_REG_CLIPBOARD
].type
= REGISTER_CLIPBOARD
;
698 array_init(&vis
->motions
);
699 array_init(&vis
->textobjects
);
700 array_init(&vis
->bindings
);
701 array_init(&vis
->actions_user
);
702 action_reset(&vis
->action
);
703 buffer_init(&vis
->input_queue
);
704 if (!(vis
->command_file
= file_new_internal(vis
, NULL
)))
706 if (!(vis
->search_file
= file_new_internal(vis
, NULL
)))
708 if (!(vis
->error_file
= file_new_internal(vis
, NULL
)))
710 if (!(vis
->actions
= map_new()))
712 if (!(vis
->keymap
= map_new()))
717 char *shell
= getenv("SHELL");
718 if ((!shell
|| !*shell
) && (pw
= getpwuid(getuid())))
719 shell
= pw
->pw_shell
;
720 if (!shell
|| !*shell
)
722 if (!(vis
->shell
= strdup(shell
)))
724 vis
->mode_prev
= vis
->mode
= &vis_modes
[VIS_MODE_NORMAL
];
727 if (event
->mode_insert_input
)
728 vis_modes
[VIS_MODE_INSERT
].input
= event
->mode_insert_input
;
729 if (event
->mode_replace_input
)
730 vis_modes
[VIS_MODE_REPLACE
].input
= event
->mode_replace_input
;
738 void vis_free(Vis
*vis
) {
741 vis_event_emit(vis
, VIS_EVENT_QUIT
);
744 vis_window_close(vis
->windows
);
745 file_free(vis
, vis
->command_file
);
746 file_free(vis
, vis
->search_file
);
747 file_free(vis
, vis
->error_file
);
748 for (int i
= 0; i
< LENGTH(vis
->registers
); i
++)
749 register_release(&vis
->registers
[i
]);
750 vis
->ui
->free(vis
->ui
);
753 while (map_first(vis
->usercmds
, &name
) && vis_cmd_unregister(vis
, name
));
755 map_free(vis
->usercmds
);
757 map_free(vis
->options
);
758 map_free(vis
->actions
);
759 map_free(vis
->keymap
);
760 buffer_release(&vis
->input_queue
);
761 for (int i
= 0; i
< VIS_MODE_INVALID
; i
++)
762 map_free(vis_modes
[i
].bindings
);
763 array_release_full(&vis
->motions
);
764 array_release_full(&vis
->textobjects
);
765 while (array_length(&vis
->bindings
))
766 vis_binding_free(vis
, array_get_ptr(&vis
->bindings
, 0));
767 array_release(&vis
->bindings
);
768 while (array_length(&vis
->actions_user
))
769 vis_action_free(vis
, array_get_ptr(&vis
->actions_user
, 0));
770 array_release(&vis
->actions_user
);
775 void vis_insert(Vis
*vis
, size_t pos
, const char *data
, size_t len
) {
776 text_insert(vis
->win
->file
->text
, pos
, data
, len
);
777 windows_invalidate(vis
, pos
, pos
+ len
);
780 void vis_insert_key(Vis
*vis
, const char *data
, size_t len
) {
781 for (Cursor
*c
= view_cursors(vis
->win
->view
); c
; c
= view_cursors_next(c
)) {
782 size_t pos
= view_cursors_pos(c
);
783 vis_insert(vis
, pos
, data
, len
);
784 view_cursors_scroll_to(c
, pos
+ len
);
788 void vis_replace(Vis
*vis
, size_t pos
, const char *data
, size_t len
) {
789 Text
*txt
= vis
->win
->file
->text
;
790 Iterator it
= text_iterator_get(txt
, pos
);
791 int chars
= text_char_count(data
, len
);
792 for (char c
; chars
-- > 0 && text_iterator_byte_get(&it
, &c
) && c
!= '\r' && c
!= '\n'; )
793 text_iterator_char_next(&it
, NULL
);
795 text_delete(txt
, pos
, it
.pos
- pos
);
796 vis_insert(vis
, pos
, data
, len
);
799 void vis_replace_key(Vis
*vis
, const char *data
, size_t len
) {
800 for (Cursor
*c
= view_cursors(vis
->win
->view
); c
; c
= view_cursors_next(c
)) {
801 size_t pos
= view_cursors_pos(c
);
802 vis_replace(vis
, pos
, data
, len
);
803 view_cursors_scroll_to(c
, pos
+ len
);
807 void vis_delete(Vis
*vis
, size_t pos
, size_t len
) {
808 text_delete(vis
->win
->file
->text
, pos
, len
);
809 windows_invalidate(vis
, pos
, pos
+ len
);
812 bool vis_action_register(Vis
*vis
, const KeyAction
*action
) {
813 return map_put(vis
->actions
, action
->name
, action
);
816 bool vis_keymap_add(Vis
*vis
, const char *key
, const char *mapping
) {
817 return map_put(vis
->keymap
, key
, mapping
);
820 void vis_keymap_disable(Vis
*vis
) {
821 vis
->keymap_disabled
= true;
824 static void window_jumplist_add(Win
*win
, size_t pos
) {
825 Mark mark
= text_mark_set(win
->file
->text
, pos
);
826 if (mark
&& win
->jumplist
)
827 ringbuf_add(win
->jumplist
, (void*)mark
);
830 static void window_jumplist_invalidate(Win
*win
) {
832 ringbuf_invalidate(win
->jumplist
);
835 void vis_do(Vis
*vis
) {
837 File
*file
= win
->file
;
838 Text
*txt
= file
->text
;
839 View
*view
= win
->view
;
840 Action
*a
= &vis
->action
;
842 if (a
->op
== &vis_operators
[VIS_OP_FILTER
] && !vis
->mode
->visual
)
843 vis_mode_switch(vis
, VIS_MODE_VISUAL_LINE
);
845 int count
= MAX(a
->count
, 1);
846 if (a
->op
== &vis_operators
[VIS_OP_MODESWITCH
])
847 count
= 1; /* count should apply to inserted text not motion */
848 bool repeatable
= a
->op
&& !vis
->macro_operator
&& !vis
->win
->parent
;
849 bool multiple_cursors
= view_cursors_multiple(view
);
850 bool linewise
= !(a
->type
& CHARWISE
) && (
851 a
->type
& LINEWISE
|| (a
->movement
&& a
->movement
->type
& LINEWISE
) ||
852 vis
->mode
== &vis_modes
[VIS_MODE_VISUAL_LINE
]);
854 for (Cursor
*cursor
= view_cursors(view
), *next
; cursor
; cursor
= next
) {
856 next
= view_cursors_next(cursor
);
857 size_t pos
= view_cursors_pos(cursor
);
858 Register
*reg
= multiple_cursors
? view_cursors_register(cursor
) : a
->reg
;
860 reg
= &vis
->registers
[file
->internal
? VIS_REG_PROMPT
: VIS_REG_DEFAULT
];
862 OperatorContext c
= {
866 .range
= text_range_empty(),
868 .linewise
= linewise
,
875 for (int i
= 0; i
< count
; i
++) {
876 size_t pos_prev
= pos
;
877 if (a
->movement
->txt
)
878 pos
= a
->movement
->txt(txt
, pos
);
879 else if (a
->movement
->cur
)
880 pos
= a
->movement
->cur(cursor
);
881 else if (a
->movement
->file
)
882 pos
= a
->movement
->file(vis
, file
, pos
);
883 else if (a
->movement
->vis
)
884 pos
= a
->movement
->vis(vis
, txt
, pos
);
885 else if (a
->movement
->view
)
886 pos
= a
->movement
->view(vis
, view
);
887 else if (a
->movement
->win
)
888 pos
= a
->movement
->win(vis
, win
, pos
);
889 else if (a
->movement
->user
)
890 pos
= a
->movement
->user(vis
, win
, a
->movement
->data
, pos
);
891 if (pos
== EPOS
|| a
->movement
->type
& IDEMPOTENT
|| pos
== pos_prev
) {
892 err
= a
->movement
->type
& COUNT_EXACT
;
903 c
.range
.start
= start
;
907 c
.range
= text_range_new(start
, pos
);
912 if (a
->movement
->type
& CHARWISE
)
913 view_cursors_scroll_to(cursor
, pos
);
915 view_cursors_to(cursor
, pos
);
916 if (vis
->mode
->visual
)
917 c
.range
= view_cursors_selection_get(cursor
);
918 if (a
->movement
->type
& JUMP
)
919 window_jumplist_add(win
, pos
);
921 window_jumplist_invalidate(win
);
922 } else if (a
->movement
->type
& INCLUSIVE
&& c
.range
.end
> start
) {
923 c
.range
.end
= text_char_next(txt
, c
.range
.end
);
924 } else if (linewise
&& (a
->movement
->type
& LINEWISE_INCLUSIVE
)) {
925 c
.range
.end
= text_char_next(txt
, c
.range
.end
);
927 } else if (a
->textobj
) {
928 if (vis
->mode
->visual
)
929 c
.range
= view_cursors_selection_get(cursor
);
931 c
.range
.start
= c
.range
.end
= pos
;
932 for (int i
= 0; i
< count
; i
++) {
933 Filerange r
= text_range_empty();
935 r
= a
->textobj
->txt(txt
, pos
);
936 else if (a
->textobj
->vis
)
937 r
= a
->textobj
->vis(vis
, txt
, pos
);
938 else if (a
->textobj
->user
)
939 r
= a
->textobj
->user(vis
, win
, a
->textobj
->data
, pos
);
940 if (!text_range_valid(&r
))
942 if (a
->textobj
->type
& TEXTOBJECT_DELIMITED_OUTER
) {
947 if (vis
->mode
->visual
|| (i
> 0 && !(a
->textobj
->type
& TEXTOBJECT_NON_CONTIGUOUS
)))
948 c
.range
= text_range_union(&c
.range
, &r
);
953 if (a
->textobj
->type
& TEXTOBJECT_EXTEND_BACKWARD
) {
955 if ((a
->textobj
->type
& TEXTOBJECT_DELIMITED_INNER
) && pos
> 0)
959 if (a
->textobj
->type
& TEXTOBJECT_DELIMITED_INNER
)
964 } else if (vis
->mode
->visual
) {
965 c
.range
= view_cursors_selection_get(cursor
);
966 if (!text_range_valid(&c
.range
))
967 c
.range
.start
= c
.range
.end
= pos
;
970 if (linewise
&& vis
->mode
!= &vis_modes
[VIS_MODE_VISUAL
])
971 c
.range
= text_range_linewise(txt
, &c
.range
);
972 if (vis
->mode
->visual
) {
973 view_cursors_selection_set(cursor
, &c
.range
);
974 if (vis
->mode
== &vis_modes
[VIS_MODE_VISUAL
] || a
->textobj
)
975 view_cursors_selection_sync(cursor
);
979 size_t pos
= a
->op
->func(vis
, txt
, &c
);
981 view_cursors_dispose(cursor
);
982 } else if (pos
<= text_size(txt
)) {
983 /* moving the cursor will affect the selection.
984 * because we want to be able to later restore
985 * the old selection we update it again before
986 * leaving visual mode.
988 Filerange sel
= view_cursors_selection_get(cursor
);
989 view_cursors_to(cursor
, pos
);
990 if (vis
->mode
->visual
) {
991 if (sel
.start
== EPOS
&& sel
.end
== EPOS
)
993 else if (sel
.start
== EPOS
)
994 sel
= text_range_new(c
.range
.start
, sel
.end
);
995 else if (sel
.end
== EPOS
)
996 sel
= text_range_new(c
.range
.start
, sel
.start
);
997 if (vis
->mode
== &vis_modes
[VIS_MODE_VISUAL_LINE
])
998 sel
= text_range_linewise(txt
, &sel
);
999 if (!text_range_contains(&sel
, pos
)) {
1000 Filerange cur
= text_range_new(pos
, pos
);
1001 sel
= text_range_union(&sel
, &cur
);
1003 view_cursors_selection_set(cursor
, &sel
);
1010 /* we do not support visual repeat, still do something resonable */
1011 if (vis
->mode
->visual
&& !a
->movement
&& !a
->textobj
)
1012 a
->movement
= &vis_motions
[VIS_MOVE_NOP
];
1014 /* operator implementations must not change the mode,
1015 * they might get called multiple times (once for every cursor)
1017 if (a
->op
== &vis_operators
[VIS_OP_CHANGE
]) {
1018 vis_mode_switch(vis
, VIS_MODE_INSERT
);
1019 } else if (a
->op
== &vis_operators
[VIS_OP_MODESWITCH
]) {
1020 vis_mode_switch(vis
, a
->mode
);
1021 } else if (a
->op
== &vis_operators
[VIS_OP_FILTER
]) {
1023 vis_cmd(vis
, a
->arg
.s
);
1025 vis_prompt_show(vis
, ":|");
1026 } else if (vis
->mode
== &vis_modes
[VIS_MODE_OPERATOR_PENDING
]) {
1027 mode_set(vis
, vis
->mode_prev
);
1028 } else if (vis
->mode
->visual
) {
1029 vis_mode_switch(vis
, VIS_MODE_NORMAL
);
1032 if (vis
->mode
== &vis_modes
[VIS_MODE_NORMAL
])
1033 vis_file_snapshot(vis
, file
);
1037 if (a
!= &vis
->action_prev
) {
1040 a
->macro
= vis
->macro_operator
;
1041 vis
->action_prev
= *a
;
1047 void action_reset(Action
*a
) {
1048 memset(a
, 0, sizeof(*a
));
1049 a
->count
= VIS_COUNT_UNKNOWN
;
1052 void vis_cancel(Vis
*vis
) {
1053 action_reset(&vis
->action
);
1056 void vis_die(Vis
*vis
, const char *msg
, ...) {
1059 vis
->ui
->die(vis
->ui
, msg
, ap
);
1063 const char *vis_keys_next(Vis
*vis
, const char *keys
) {
1064 if (!keys
|| !*keys
)
1067 TermKey
*termkey
= vis
->ui
->termkey_get(vis
->ui
);
1068 const char *next
= NULL
;
1069 /* first try to parse a special key of the form <Key> */
1070 if (*keys
== '<' && keys
[1] && (next
= termkey_strpkey(termkey
, keys
+1, &key
, TERMKEY_FORMAT_VIM
)) && *next
== '>')
1072 if (strncmp(keys
, "<vis-", 5) == 0) {
1073 const char *start
= keys
+ 1, *end
= start
;
1074 while (*end
&& *end
!= '>')
1076 if (end
> start
&& end
- start
- 1 < VIS_KEY_LENGTH_MAX
&& *end
== '>') {
1077 char key
[VIS_KEY_LENGTH_MAX
];
1078 memcpy(key
, start
, end
- start
);
1079 key
[end
- start
] = '\0';
1080 if (map_get(vis
->actions
, key
))
1086 while (!ISUTF8(*keys
))
1091 long vis_keys_codepoint(Vis
*vis
, const char *keys
) {
1092 long codepoint
= -1;
1095 TermKey
*termkey
= vis
->ui
->termkey_get(vis
->ui
);
1099 if (keys
[0] == '<' && !keys
[1])
1102 if (keys
[0] == '<' && (next
= termkey_strpkey(termkey
, keys
+1, &key
, TERMKEY_FORMAT_VIM
)) && *next
== '>')
1103 codepoint
= (key
.type
== TERMKEY_TYPE_UNICODE
) ? key
.code
.codepoint
: -1;
1104 else if ((next
= termkey_strpkey(termkey
, keys
, &key
, TERMKEY_FORMAT_VIM
)))
1105 codepoint
= (key
.type
== TERMKEY_TYPE_UNICODE
) ? key
.code
.codepoint
: -1;
1107 if (codepoint
!= -1) {
1108 if (key
.modifiers
== TERMKEY_KEYMOD_CTRL
)
1113 if (!next
|| key
.type
!= TERMKEY_TYPE_KEYSYM
)
1116 const int keysym
[] = {
1117 TERMKEY_SYM_ENTER
, '\n',
1118 TERMKEY_SYM_TAB
, '\t',
1119 TERMKEY_SYM_BACKSPACE
, '\b',
1120 TERMKEY_SYM_ESCAPE
, 0x1b,
1121 TERMKEY_SYM_DELETE
, 0x7f,
1125 for (const int *k
= keysym
; k
[0]; k
+= 2) {
1126 if (key
.code
.sym
== k
[0])
1133 bool vis_keys_utf8(Vis
*vis
, const char *keys
, char utf8
[static UTFmax
+1]) {
1134 Rune rune
= vis_keys_codepoint(vis
, keys
);
1135 if (rune
== (Rune
)-1)
1137 size_t len
= runetochar(utf8
, &rune
);
1142 static void vis_keys_process(Vis
*vis
, size_t pos
) {
1143 Buffer
*buf
= &vis
->input_queue
;
1144 char *keys
= buf
->data
+ pos
, *start
= keys
, *cur
= keys
, *end
= keys
, *binding_end
= keys
;;
1145 bool prefix
= false;
1146 KeyBinding
*binding
= NULL
;
1148 while (cur
&& *cur
) {
1150 if (!(end
= (char*)vis_keys_next(vis
, cur
))) {
1151 buffer_remove(buf
, keys
- buf
->data
, strlen(keys
));
1159 for (Mode
*global_mode
= vis
->mode
; global_mode
&& !prefix
; global_mode
= global_mode
->parent
) {
1160 for (int global
= 0; global
< 2 && !prefix
; global
++) {
1161 Mode
*mode
= (global
|| !vis
->win
) ?
1163 &vis
->win
->modes
[global_mode
->id
];
1164 if (!mode
->bindings
)
1166 /* keep track of longest matching binding */
1167 KeyBinding
*match
= map_get(mode
->bindings
, start
);
1168 if (match
&& end
> binding_end
) {
1172 /* "<" is never treated as a prefix because it
1173 * is used to denote special key symbols */
1174 if (strcmp(start
, "<")) {
1175 prefix
= (!match
&& map_contains(mode
->bindings
, start
)) ||
1176 (match
&& !map_leaf(mode
->bindings
, start
));
1184 /* input sofar is ambigious, wait for more */
1187 } else if (binding
) { /* exact match */
1188 if (binding
->action
) {
1189 size_t len
= binding_end
- start
;
1190 strcpy(vis
->key_prev
, vis
->key_current
);
1191 strncpy(vis
->key_current
, start
, len
);
1192 vis
->key_current
[len
] = '\0';
1193 end
= (char*)binding
->action
->func(vis
, binding_end
, &binding
->action
->arg
);
1199 } else if (binding
->alias
) {
1200 buffer_remove(buf
, start
- buf
->data
, binding_end
- start
);
1201 buffer_insert0(buf
, start
- buf
->data
, binding
->alias
);
1205 binding_end
= start
;
1206 } else { /* no keybinding */
1207 KeyAction
*action
= NULL
;
1208 if (start
[0] == '<' && end
[-1] == '>') {
1209 /* test for special editor key command */
1212 action
= map_get(vis
->actions
, start
+1);
1215 size_t len
= end
- start
;
1216 strcpy(vis
->key_prev
, vis
->key_current
);
1217 strncpy(vis
->key_current
, start
, len
);
1218 vis
->key_current
[len
] = '\0';
1219 end
= (char*)action
->func(vis
, end
, &action
->arg
);
1226 if (!action
&& vis
->mode
->input
) {
1227 end
= (char*)vis_keys_next(vis
, start
);
1228 vis
->mode
->input(vis
, start
, end
- start
);
1234 buffer_remove(buf
, keys
- buf
->data
, end
- keys
);
1237 void vis_keys_feed(Vis
*vis
, const char *input
) {
1242 if (!macro_append(¯o
, input
))
1244 /* use internal function, to keep Lua based tests which use undo points working */
1245 macro_replay_internal(vis
, ¯o
);
1246 macro_release(¯o
);
1249 static void vis_keys_push(Vis
*vis
, const char *input
, size_t pos
, bool record
) {
1252 if (record
&& vis
->recording
)
1253 macro_append(vis
->recording
, input
);
1254 if (vis
->macro_operator
)
1255 macro_append(vis
->macro_operator
, input
);
1256 if (buffer_append0(&vis
->input_queue
, input
))
1257 vis_keys_process(vis
, pos
);
1260 static const char *getkey(Vis
*vis
) {
1261 TermKeyKey key
= { 0 };
1262 if (!vis
->ui
->getkey(vis
->ui
, &key
))
1265 bool use_keymap
= vis
->mode
->id
!= VIS_MODE_INSERT
&&
1266 vis
->mode
->id
!= VIS_MODE_REPLACE
&&
1267 !vis
->keymap_disabled
;
1268 vis
->keymap_disabled
= false;
1269 if (key
.type
== TERMKEY_TYPE_UNICODE
&& use_keymap
) {
1270 const char *mapped
= map_get(vis
->keymap
, key
.utf8
);
1272 size_t len
= strlen(mapped
)+1;
1273 if (len
<= sizeof(key
.utf8
))
1274 memcpy(key
.utf8
, mapped
, len
);
1278 TermKey
*termkey
= vis
->ui
->termkey_get(vis
->ui
);
1279 termkey_strfkey(termkey
, vis
->key
, sizeof(vis
->key
), &key
, TERMKEY_FORMAT_VIM
);
1283 bool vis_signal_handler(Vis
*vis
, int signum
, const siginfo_t
*siginfo
, const void *context
) {
1286 for (File
*file
= vis
->files
; file
; file
= file
->next
) {
1287 if (text_sigbus(file
->text
, siginfo
->si_addr
))
1288 file
->truncated
= true;
1292 siglongjmp(vis
->sigbus_jmpbuf
, 1);
1295 vis
->cancel_filter
= true;
1301 vis
->need_resize
= true;
1305 vis
->terminate
= true;
1311 int vis_run(Vis
*vis
, int argc
, char *argv
[]) {
1313 return EXIT_SUCCESS
;
1314 if (vis
->exit_status
!= -1)
1315 return vis
->exit_status
;
1316 vis
->running
= true;
1318 vis_event_emit(vis
, VIS_EVENT_START
);
1320 struct timespec idle
= { .tv_nsec
= 0 }, *timeout
= NULL
;
1323 sigemptyset(&emptyset
);
1325 vis
->exit_status
= EXIT_SUCCESS
;
1327 sigsetjmp(vis
->sigbus_jmpbuf
, 1);
1329 while (vis
->running
) {
1332 FD_SET(STDIN_FILENO
, &fds
);
1336 for (Win
*next
, *win
= vis
->windows
; win
; win
= next
) {
1338 if (win
->file
->truncated
) {
1340 name
= strdup(win
->file
->name
);
1341 vis_window_close(win
);
1345 vis_die(vis
, "WARNING: file `%s' truncated!\n", name
? name
: "-");
1347 vis_info_show(vis
, "WARNING: file `%s' truncated!\n", name
? name
: "-");
1348 vis
->sigbus
= false;
1353 vis_die(vis
, "Killed by SIGTERM\n");
1356 vis
->ui
->resume(vis
->ui
);
1357 vis
->resume
= false;
1359 if (vis
->need_resize
) {
1360 vis
->ui
->resize(vis
->ui
);
1361 vis
->need_resize
= false;
1365 idle
.tv_sec
= vis
->mode
->idle_timeout
;
1366 int r
= pselect(1, &fds
, NULL
, NULL
, timeout
, &emptyset
);
1367 if (r
== -1 && errno
== EINTR
)
1371 /* TODO save all pending changes to a ~suffixed file */
1372 vis_die(vis
, "Error in mainloop: %s\n", strerror(errno
));
1375 if (!FD_ISSET(STDIN_FILENO
, &fds
)) {
1376 if (vis
->mode
->idle
)
1377 vis
->mode
->idle(vis
);
1382 TermKey
*termkey
= vis
->ui
->termkey_get(vis
->ui
);
1383 termkey_advisereadable(termkey
);
1386 while ((key
= getkey(vis
)))
1387 vis_keys_push(vis
, key
, 0, true);
1389 if (vis
->mode
->idle
)
1392 return vis
->exit_status
;
1395 static Macro
*macro_get(Vis
*vis
, enum VisRegister id
) {
1396 if (id
== VIS_MACRO_LAST_RECORDED
)
1397 return vis
->last_recording
;
1398 if (VIS_REG_A
<= id
&& id
<= VIS_REG_Z
)
1400 if (id
< LENGTH(vis
->registers
))
1401 return &vis
->registers
[id
].buf
;
1405 void macro_operator_record(Vis
*vis
) {
1406 if (vis
->macro_operator
)
1408 vis
->macro_operator
= macro_get(vis
, VIS_MACRO_OPERATOR
);
1409 macro_reset(vis
->macro_operator
);
1412 void macro_operator_stop(Vis
*vis
) {
1413 if (!vis
->macro_operator
)
1415 Macro
*dot
= macro_get(vis
, VIS_REG_DOT
);
1416 buffer_put(dot
, vis
->macro_operator
->data
, vis
->macro_operator
->len
);
1417 vis
->action_prev
.macro
= dot
;
1418 vis
->macro_operator
= NULL
;
1421 bool vis_macro_record(Vis
*vis
, enum VisRegister id
) {
1422 Macro
*macro
= macro_get(vis
, id
);
1423 if (vis
->recording
|| !macro
)
1425 if (!(VIS_REG_A
<= id
&& id
<= VIS_REG_Z
))
1427 vis
->recording
= macro
;
1428 vis_event_emit(vis
, VIS_EVENT_WIN_STATUS
, vis
->win
);
1432 bool vis_macro_record_stop(Vis
*vis
) {
1433 if (!vis
->recording
)
1435 /* XXX: hack to remove last recorded key, otherwise upon replay
1436 * we would start another recording */
1437 if (vis
->recording
->len
> 1) {
1438 vis
->recording
->len
--;
1439 vis
->recording
->data
[vis
->recording
->len
-1] = '\0';
1441 vis
->last_recording
= vis
->recording
;
1442 vis
->recording
= NULL
;
1443 vis_event_emit(vis
, VIS_EVENT_WIN_STATUS
, vis
->win
);
1447 bool vis_macro_recording(Vis
*vis
) {
1448 return vis
->recording
;
1451 static void macro_replay(Vis
*vis
, const Macro
*macro
) {
1452 const Macro
*replaying
= vis
->replaying
;
1453 vis
->replaying
= macro
;
1454 macro_replay_internal(vis
, macro
);
1455 vis
->replaying
= replaying
;
1458 static void macro_replay_internal(Vis
*vis
, const Macro
*macro
) {
1459 size_t pos
= buffer_length0(&vis
->input_queue
);
1460 for (char *key
= macro
->data
, *next
; key
; key
= next
) {
1462 next
= (char*)vis_keys_next(vis
, key
);
1468 vis_keys_push(vis
, key
, pos
, false);
1475 bool vis_macro_replay(Vis
*vis
, enum VisRegister id
) {
1476 if (id
== VIS_REG_SEARCH
)
1477 return vis_motion(vis
, VIS_MOVE_SEARCH_NEXT
);
1478 if (id
== VIS_REG_COMMAND
) {
1479 const char *cmd
= register_get(vis
, &vis
->registers
[id
], NULL
);
1480 return vis_cmd(vis
, cmd
);
1483 Macro
*macro
= macro_get(vis
, id
);
1484 if (!macro
|| macro
== vis
->recording
)
1486 int count
= vis_count_get_default(vis
, 1);
1488 for (int i
= 0; i
< count
; i
++)
1489 macro_replay(vis
, macro
);
1490 vis_file_snapshot(vis
, vis
->win
->file
);
1494 void vis_repeat(Vis
*vis
) {
1495 const Macro
*macro
= vis
->action_prev
.macro
;
1496 int count
= vis
->action
.count
;
1497 if (count
!= VIS_COUNT_UNKNOWN
)
1498 vis
->action_prev
.count
= count
;
1500 count
= vis
->action_prev
.count
;
1501 vis
->action
= vis
->action_prev
;
1504 Mode
*mode
= vis
->mode
;
1505 Action action_prev
= vis
->action_prev
;
1507 action_prev
.op
== &vis_operators
[VIS_OP_CHANGE
] ||
1508 action_prev
.op
== &vis_operators
[VIS_OP_FILTER
])
1510 if (vis
->action_prev
.op
== &vis_operators
[VIS_OP_MODESWITCH
])
1511 vis
->action_prev
.count
= 1;
1512 for (int i
= 0; i
< count
; i
++) {
1513 mode_set(vis
, mode
);
1514 macro_replay(vis
, macro
);
1516 vis
->action_prev
= action_prev
;
1519 vis_file_snapshot(vis
, vis
->win
->file
);
1522 enum VisMark
vis_mark_from(Vis
*vis
, char mark
) {
1523 if (mark
>= 'a' && mark
<= 'z')
1524 return VIS_MARK_a
+ mark
- 'a';
1525 for (size_t i
= 0; i
< LENGTH(vis_marks
); i
++) {
1526 if (vis_marks
[i
].name
== mark
)
1529 return VIS_MARK_INVALID
;
1532 void vis_mark_set(Vis
*vis
, enum VisMark mark
, size_t pos
) {
1533 File
*file
= vis
->win
->file
;
1534 if (mark
< LENGTH(file
->marks
))
1535 file
->marks
[mark
] = text_mark_set(file
->text
, pos
);
1538 int vis_count_get(Vis
*vis
) {
1539 return vis
->action
.count
;
1542 int vis_count_get_default(Vis
*vis
, int def
) {
1543 if (vis
->action
.count
== VIS_COUNT_UNKNOWN
)
1545 return vis
->action
.count
;
1548 void vis_count_set(Vis
*vis
, int count
) {
1549 vis
->action
.count
= (count
>= 0 ? count
: VIS_COUNT_UNKNOWN
);
1552 enum VisRegister
vis_register_from(Vis
*vis
, char reg
) {
1554 case '+': return VIS_REG_CLIPBOARD
;
1555 case '@': return VIS_MACRO_LAST_RECORDED
;
1558 if ('a' <= reg
&& reg
<= 'z')
1559 return VIS_REG_a
+ reg
- 'a';
1560 if ('A' <= reg
&& reg
<= 'Z')
1561 return VIS_REG_A
+ reg
- 'A';
1562 for (size_t i
= 0; i
< LENGTH(vis_registers
); i
++) {
1563 if (vis_registers
[i
].name
== reg
)
1566 return VIS_REG_INVALID
;
1569 void vis_register_set(Vis
*vis
, enum VisRegister reg
) {
1570 if (VIS_REG_A
<= reg
&& reg
<= VIS_REG_Z
) {
1571 vis
->action
.reg
= &vis
->registers
[VIS_REG_a
+ reg
- VIS_REG_A
];
1572 vis
->action
.reg
->append
= true;
1573 } else if (reg
< LENGTH(vis
->registers
)) {
1574 vis
->action
.reg
= &vis
->registers
[reg
];
1575 vis
->action
.reg
->append
= false;
1579 const char *vis_register_get(Vis
*vis
, enum VisRegister reg
, size_t *len
) {
1580 if (VIS_REG_A
<= reg
&& reg
<= VIS_REG_Z
)
1581 reg
= VIS_REG_a
+ reg
- VIS_REG_A
;
1582 if (reg
< LENGTH(vis
->registers
))
1583 return register_get(vis
, &vis
->registers
[reg
], len
);
1588 void vis_exit(Vis
*vis
, int status
) {
1589 vis
->running
= false;
1590 vis
->exit_status
= status
;
1593 void vis_insert_tab(Vis
*vis
) {
1594 if (!vis
->expandtab
) {
1595 vis_insert_key(vis
, "\t", 1);
1599 int tabwidth
= MIN(vis
->tabwidth
, LENGTH(spaces
) - 1);
1600 for (Cursor
*c
= view_cursors(vis
->win
->view
); c
; c
= view_cursors_next(c
)) {
1601 size_t pos
= view_cursors_pos(c
);
1602 int width
= text_line_width_get(vis
->win
->file
->text
, pos
);
1603 int count
= tabwidth
- (width
% tabwidth
);
1604 for (int i
= 0; i
< count
; i
++)
1606 spaces
[count
] = '\0';
1607 vis_insert(vis
, pos
, spaces
, count
);
1608 view_cursors_scroll_to(c
, pos
+ count
);
1612 size_t vis_text_insert_nl(Vis
*vis
, Text
*txt
, size_t pos
) {
1613 const char *nl
= text_newline_char(txt
);
1614 size_t nl_len
= strlen(nl
), indent_len
= 0;
1615 char byte
, *indent
= NULL
;
1616 /* insert second newline at end of file, except if there is already one */
1617 bool eof
= pos
== text_size(txt
);
1618 bool nl2
= eof
&& !(pos
> 0 && text_byte_get(txt
, pos
-1, &byte
) && byte
== '\n');
1620 if (vis
->autoindent
) {
1621 /* copy leading white space of current line */
1622 size_t begin
= text_line_begin(txt
, pos
);
1623 size_t start
= text_line_start(txt
, begin
);
1624 size_t end
= text_line_end(txt
, start
);
1627 indent_len
= start
>= begin
? start
-begin
: 0;
1631 indent
= malloc(indent_len
+1);
1633 indent_len
= text_bytes_get(txt
, begin
, indent_len
, indent
);
1637 text_insert(txt
, pos
, nl
, nl_len
);
1640 text_insert(txt
, pos
, nl
, nl_len
);
1642 pos
-= nl_len
; /* place cursor before, not after nl */
1647 text_insert(txt
, pos
, indent
, indent_len
);
1649 return pos
+ indent_len
;
1652 void vis_insert_nl(Vis
*vis
) {
1653 View
*view
= vis
->win
->view
;
1654 Text
*txt
= vis
->win
->file
->text
;
1655 for (Cursor
*c
= view_cursors(view
); c
; c
= view_cursors_next(c
)) {
1656 size_t pos
= view_cursors_pos(c
);
1657 size_t newpos
= vis_text_insert_nl(vis
, txt
, pos
);
1658 /* This is a bit of a hack to fix cursor positioning when
1659 * inserting a new line at the start of the view port.
1660 * It has the effect of reseting the mark used by the view
1661 * code to keep track of the start of the visible region.
1663 view_cursors_to(c
, pos
);
1664 view_cursors_to(c
, newpos
);
1666 size_t pos
= view_cursor_get(view
);
1667 windows_invalidate(vis
, pos
, pos
-1);
1670 Regex
*vis_regex(Vis
*vis
, const char *pattern
) {
1671 if (!pattern
&& !(pattern
= register_get(vis
, &vis
->registers
[VIS_REG_SEARCH
], NULL
)))
1673 Regex
*regex
= text_regex_new();
1676 if (text_regex_compile(regex
, pattern
, REG_EXTENDED
|REG_NEWLINE
) != 0) {
1677 text_regex_free(regex
);
1680 register_put0(vis
, &vis
->registers
[VIS_REG_SEARCH
], pattern
);
1684 int vis_pipe(Vis
*vis
, File
*file
, Filerange
*range
, const char *argv
[],
1685 void *stdout_context
, ssize_t (*read_stdout
)(void *stdout_context
, char *data
, size_t len
),
1686 void *stderr_context
, ssize_t (*read_stderr
)(void *stderr_context
, char *data
, size_t len
)) {
1688 /* if an invalid range was given, stdin (i.e. key board input) is passed
1689 * through the external command. */
1690 Text
*text
= file
->text
;
1691 int pin
[2], pout
[2], perr
[2], status
= -1;
1692 bool interactive
= !text_range_valid(range
);
1693 Filerange rout
= interactive
? text_range_new(0, 0) : *range
;
1695 if (pipe(pin
) == -1)
1697 if (pipe(pout
) == -1) {
1703 if (pipe(perr
) == -1) {
1711 vis
->ui
->terminal_save(vis
->ui
);
1721 vis_info_show(vis
, "fork failure: %s", strerror(errno
));
1723 } else if (pid
== 0) { /* child i.e filter */
1724 int null
= open("/dev/null", O_WRONLY
);
1726 fprintf(stderr
, "failed to open /dev/null");
1730 dup2(pin
[0], STDIN_FILENO
);
1734 dup2(STDERR_FILENO
, STDOUT_FILENO
);
1735 else if (read_stdout
)
1736 dup2(pout
[1], STDOUT_FILENO
);
1738 dup2(null
, STDOUT_FILENO
);
1743 dup2(perr
[1], STDERR_FILENO
);
1745 dup2(null
, STDERR_FILENO
);
1752 char *name
= strrchr(file
->name
, '/');
1753 setenv("vis_filepath", file
->name
, 1);
1754 setenv("vis_filename", name
? name
+1 : file
->name
, 1);
1758 execlp(vis
->shell
, vis
->shell
, "-c", argv
[0], (char*)NULL
);
1760 execvp(argv
[0], (char* const*)argv
);
1761 fprintf(stderr
, "exec failure: %s", strerror(errno
));
1765 vis
->cancel_filter
= false;
1771 if (fcntl(pout
[0], F_SETFL
, O_NONBLOCK
) == -1 ||
1772 fcntl(perr
[0], F_SETFL
, O_NONBLOCK
) == -1)
1778 if (vis
->cancel_filter
) {
1779 kill(-pid
, SIGTERM
);
1786 FD_SET(pin
[1], &wfds
);
1788 FD_SET(pout
[0], &rfds
);
1790 FD_SET(perr
[0], &rfds
);
1792 if (select(FD_SETSIZE
, &rfds
, &wfds
, NULL
, NULL
) == -1) {
1795 vis_info_show(vis
, "Select failure");
1799 if (pin
[1] != -1 && FD_ISSET(pin
[1], &wfds
)) {
1800 Filerange junk
= rout
;
1801 if (junk
.end
> junk
.start
+ PIPE_BUF
)
1802 junk
.end
= junk
.start
+ PIPE_BUF
;
1803 ssize_t len
= text_write_range(text
, &junk
, pin
[1]);
1806 if (text_range_size(&rout
) == 0) {
1814 vis_info_show(vis
, "Error writing to external command");
1818 if (pout
[0] != -1 && FD_ISSET(pout
[0], &rfds
)) {
1820 ssize_t len
= read(pout
[0], buf
, sizeof buf
);
1823 (*read_stdout
)(stdout_context
, buf
, len
);
1824 } else if (len
== 0) {
1827 } else if (errno
!= EINTR
&& errno
!= EWOULDBLOCK
) {
1828 vis_info_show(vis
, "Error reading from filter stdout");
1834 if (perr
[0] != -1 && FD_ISSET(perr
[0], &rfds
)) {
1836 ssize_t len
= read(perr
[0], buf
, sizeof buf
);
1839 (*read_stderr
)(stderr_context
, buf
, len
);
1840 } else if (len
== 0) {
1843 } else if (errno
!= EINTR
&& errno
!= EWOULDBLOCK
) {
1844 vis_info_show(vis
, "Error reading from filter stderr");
1850 } while (pin
[1] != -1 || pout
[0] != -1 || perr
[0] != -1);
1860 for (pid_t died
; (died
= waitpid(pid
, &status
, 0)) != -1 && pid
!= died
;);
1862 vis
->ui
->terminal_restore(vis
->ui
);
1867 static ssize_t
read_buffer(void *context
, char *data
, size_t len
) {
1868 buffer_append(context
, data
, len
);
1872 int vis_pipe_collect(Vis
*vis
, File
*file
, Filerange
*range
, const char *argv
[], char **out
, char **err
) {
1873 Buffer bufout
, buferr
;
1874 buffer_init(&bufout
);
1875 buffer_init(&buferr
);
1876 int status
= vis_pipe(vis
, file
, range
, argv
,
1877 &bufout
, out
? read_buffer
: NULL
,
1878 &buferr
, err
? read_buffer
: NULL
);
1879 buffer_terminate(&bufout
);
1880 buffer_terminate(&buferr
);
1882 *out
= buffer_move(&bufout
);
1884 *err
= buffer_move(&buferr
);
1885 buffer_release(&bufout
);
1886 buffer_release(&buferr
);
1890 bool vis_cmd(Vis
*vis
, const char *cmdline
) {
1893 while (*cmdline
== ':')
1895 size_t len
= strlen(cmdline
);
1896 char *line
= malloc(len
+2);
1899 strncpy(line
, cmdline
, len
+1);
1901 for (char *end
= line
+ len
- 1; end
>= line
&& isspace((unsigned char)*end
); end
--)
1904 enum SamError err
= sam_cmd(vis
, line
);
1905 if (err
!= SAM_ERR_OK
)
1906 vis_info_show(vis
, "%s", sam_error(err
));
1908 return err
== SAM_ERR_OK
;
1911 void vis_file_snapshot(Vis
*vis
, File
*file
) {
1912 if (!vis
->replaying
)
1913 text_snapshot(file
->text
);
1916 Text
*vis_text(Vis
*vis
) {
1917 return vis
->win
->file
->text
;
1920 View
*vis_view(Vis
*vis
) {
1921 return vis
->win
->view
;
1924 Win
*vis_window(Vis
*vis
) {
1928 bool vis_get_autoindent(const Vis
*vis
) {
1929 return vis
->autoindent
;