filetype: Set "groovy" for Jenkinsfile
[vis.git] / vis-modes.c
blob2e120aab3db04a7f9de977ab8189056ec605fc7a
1 #include <string.h>
2 #include <strings.h>
3 #include "vis-core.h"
4 #include "text-motions.h"
5 #include "util.h"
7 static void keyaction_free(KeyAction *action) {
8 if (!action)
9 return;
10 free((char*)action->name);
11 free(VIS_HELP_USE((char*)action->help));
12 free(action);
15 KeyAction *vis_action_new(Vis *vis, const char *name, const char *help, KeyActionFunction *func, Arg arg) {
16 KeyAction *action = calloc(1, sizeof *action);
17 if (!action)
18 return NULL;
19 if (name && !(action->name = strdup(name)))
20 goto err;
21 #if CONFIG_HELP
22 if (help && !(action->help = strdup(help)))
23 goto err;
24 #endif
25 action->func = func;
26 action->arg = arg;
27 if (!array_add_ptr(&vis->actions_user, action))
28 goto err;
29 return action;
30 err:
31 keyaction_free(action);
32 return NULL;
35 void vis_action_free(Vis *vis, KeyAction *action) {
36 if (!action)
37 return;
38 size_t len = array_length(&vis->actions_user);
39 for (size_t i = 0; i < len; i++) {
40 if (action == array_get_ptr(&vis->actions_user, i)) {
41 keyaction_free(action);
42 array_remove(&vis->actions_user, i);
43 return;
48 KeyBinding *vis_binding_new(Vis *vis) {
49 KeyBinding *binding = calloc(1, sizeof *binding);
50 if (binding && array_add_ptr(&vis->bindings, binding))
51 return binding;
52 free(binding);
53 return NULL;
56 void vis_binding_free(Vis *vis, KeyBinding *binding) {
57 if (!binding)
58 return;
59 size_t len = array_length(&vis->bindings);
60 for (size_t i = 0; i < len; i++) {
61 if (binding == array_get_ptr(&vis->bindings, i)) {
62 if (binding->alias)
63 free((char*)binding->alias);
64 if (binding->action && !binding->action->name)
65 vis_action_free(vis, (KeyAction*)binding->action);
66 free(binding);
67 array_remove(&vis->bindings, i);
68 return;
73 Mode *mode_get(Vis *vis, enum VisMode mode) {
74 if (mode < LENGTH(vis_modes))
75 return &vis_modes[mode];
76 return NULL;
79 void mode_set(Vis *vis, Mode *new_mode) {
80 if (vis->mode == new_mode)
81 return;
82 if (vis->mode->leave)
83 vis->mode->leave(vis, new_mode);
84 if (vis->mode != &vis_modes[VIS_MODE_OPERATOR_PENDING])
85 vis->mode_prev = vis->mode;
86 vis->mode = new_mode;
87 if (new_mode->enter)
88 new_mode->enter(vis, vis->mode_prev);
91 void vis_mode_switch(Vis *vis, enum VisMode mode) {
92 if (mode < LENGTH(vis_modes))
93 mode_set(vis, &vis_modes[mode]);
96 enum VisMode vis_mode_from(Vis *vis, const char *name) {
97 for (size_t i = 0; name && i < LENGTH(vis_modes); i++) {
98 Mode *mode = &vis_modes[i];
99 if (!strcasecmp(mode->name, name))
100 return mode->id;
102 return VIS_MODE_INVALID;
105 enum VisMode vis_mode_get(Vis *vis) {
106 return vis->mode->id;
109 static bool mode_unmap(Mode *mode, const char *key) {
110 return mode && mode->bindings && map_delete(mode->bindings, key);
113 bool vis_mode_unmap(Vis *vis, enum VisMode id, const char *key) {
114 return id < LENGTH(vis_modes) && mode_unmap(&vis_modes[id], key);
117 bool vis_window_mode_unmap(Win *win, enum VisMode id, const char *key) {
118 return id < LENGTH(win->modes) && mode_unmap(&win->modes[id], key);
121 static bool mode_map(Vis *vis, Mode *mode, bool force, const char *key, const KeyBinding *binding) {
122 if (!mode)
123 return false;
124 if (binding->alias && key[0] != '<' && strncmp(key, binding->alias, strlen(key)) == 0)
125 return false;
126 if (!mode->bindings && !(mode->bindings = map_new()))
127 return false;
128 if (force)
129 map_delete(mode->bindings, key);
130 return map_put(mode->bindings, key, binding);
133 bool vis_mode_map(Vis *vis, enum VisMode id, bool force, const char *key, const KeyBinding *binding) {
134 return id < LENGTH(vis_modes) && mode_map(vis, &vis_modes[id], force, key, binding);
137 bool vis_window_mode_map(Win *win, enum VisMode id, bool force, const char *key, const KeyBinding *binding) {
138 return id < LENGTH(win->modes) && mode_map(win->vis, &win->modes[id], force, key, binding);
141 /** mode switching event handlers */
143 static void vis_mode_normal_enter(Vis *vis, Mode *old) {
144 Win *win = vis->win;
145 if (!win)
146 return;
147 if (old != mode_get(vis, VIS_MODE_INSERT) && old != mode_get(vis, VIS_MODE_REPLACE))
148 return;
149 if (vis->autoindent && strcmp(vis->key_prev, "<Enter>") == 0) {
150 Text *txt = win->file->text;
151 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s)) {
152 size_t pos = view_cursors_pos(s);
153 size_t start = text_line_start(txt, pos);
154 size_t end = text_line_end(txt, pos);
155 if (start == pos && start == end) {
156 size_t begin = text_line_begin(txt, pos);
157 size_t len = start - begin;
158 if (len) {
159 text_delete(txt, begin, len);
160 view_cursors_to(s, pos-len);
165 macro_operator_stop(vis);
166 if (!win->parent && vis->action_prev.op == &vis_operators[VIS_OP_MODESWITCH] &&
167 vis->action_prev.count > 1) {
168 /* temporarily disable motion, in something like `5atext`
169 * we should only move the cursor once then insert the text */
170 const Movement *motion = vis->action_prev.movement;
171 if (motion)
172 vis->action_prev.movement = &vis_motions[VIS_MOVE_NOP];
173 /* we already inserted the text once, so temporarily decrease count */
174 vis->action_prev.count--;
175 vis_repeat(vis);
176 vis->action_prev.count++;
177 vis->action_prev.movement = motion;
179 /* make sure we can recover the current state after an editing operation */
180 vis_file_snapshot(vis, win->file);
183 static void vis_mode_operator_input(Vis *vis, const char *str, size_t len) {
184 /* invalid operator */
185 vis_cancel(vis);
186 mode_set(vis, vis->mode_prev);
189 static void vis_mode_visual_enter(Vis *vis, Mode *old) {
190 Win *win = vis->win;
191 if (!old->visual && win) {
192 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s))
193 view_selections_anchor(s, true);
197 static void vis_mode_visual_line_enter(Vis *vis, Mode *old) {
198 Win *win = vis->win;
199 if (!old->visual && win) {
200 for (Selection *s = view_selections(win->view); s; s = view_selections_next(s))
201 view_selections_anchor(s, true);
203 if (!vis->action.op)
204 vis_motion(vis, VIS_MOVE_NOP);
207 static void vis_mode_visual_line_leave(Vis *vis, Mode *new) {
208 Win *win = vis->win;
209 if (!win)
210 return;
211 if (!new->visual) {
212 if (!vis->action.op)
213 window_selection_save(win);
214 view_selections_clear_all(win->view);
215 } else {
216 view_cursor_to(win->view, view_cursor_get(win->view));
220 static void vis_mode_visual_leave(Vis *vis, Mode *new) {
221 Win *win = vis->win;
222 if (!new->visual && win) {
223 if (!vis->action.op)
224 window_selection_save(win);
225 view_selections_clear_all(win->view);
229 static void vis_mode_insert_replace_enter(Vis *vis, Mode *old) {
230 if (!vis->win || vis->win->parent)
231 return;
232 if (!vis->action.op) {
233 action_reset(&vis->action_prev);
234 vis->action_prev.op = &vis_operators[VIS_OP_MODESWITCH];
235 vis->action_prev.mode = vis->mode->id;
237 macro_operator_record(vis);
240 static void vis_mode_insert_idle(Vis *vis) {
241 Win *win = vis->win;
242 if (win)
243 vis_file_snapshot(vis, win->file);
246 static void vis_mode_insert_input(Vis *vis, const char *str, size_t len) {
247 vis_insert_key(vis, str, len);
250 static void vis_mode_replace_input(Vis *vis, const char *str, size_t len) {
251 vis_replace_key(vis, str, len);
254 Mode vis_modes[] = {
255 [VIS_MODE_OPERATOR_PENDING] = {
256 .id = VIS_MODE_OPERATOR_PENDING,
257 .name = "OPERATOR-PENDING",
258 .input = vis_mode_operator_input,
259 .help = "",
261 [VIS_MODE_NORMAL] = {
262 .id = VIS_MODE_NORMAL,
263 .name = "NORMAL",
264 .help = "",
265 .enter = vis_mode_normal_enter,
267 [VIS_MODE_VISUAL] = {
268 .id = VIS_MODE_VISUAL,
269 .name = "VISUAL",
270 .status = "VISUAL",
271 .help = "",
272 .enter = vis_mode_visual_enter,
273 .leave = vis_mode_visual_leave,
274 .visual = true,
276 [VIS_MODE_VISUAL_LINE] = {
277 .id = VIS_MODE_VISUAL_LINE,
278 .name = "VISUAL-LINE",
279 .parent = &vis_modes[VIS_MODE_VISUAL],
280 .status = "VISUAL-LINE",
281 .help = "",
282 .enter = vis_mode_visual_line_enter,
283 .leave = vis_mode_visual_line_leave,
284 .visual = true,
286 [VIS_MODE_INSERT] = {
287 .id = VIS_MODE_INSERT,
288 .name = "INSERT",
289 .status = "INSERT",
290 .help = "",
291 .enter = vis_mode_insert_replace_enter,
292 .input = vis_mode_insert_input,
293 .idle = vis_mode_insert_idle,
294 .idle_timeout = 3,
296 [VIS_MODE_REPLACE] = {
297 .id = VIS_MODE_REPLACE,
298 .name = "REPLACE",
299 .parent = &vis_modes[VIS_MODE_INSERT],
300 .status = "REPLACE",
301 .help = "",
302 .enter = vis_mode_insert_replace_enter,
303 .input = vis_mode_replace_input,
304 .idle = vis_mode_insert_idle,
305 .idle_timeout = 3,