build: add a make docker target
[vis.git] / vis-modes.c
blob060902257c26b1539c170c8ef1ebe8660d0b1ab0
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 if (old != mode_get(vis, VIS_MODE_INSERT) && old != mode_get(vis, VIS_MODE_REPLACE))
145 return;
146 if (vis->autoindent && strcmp(vis->key_prev, "<Enter>") == 0) {
147 Text *txt = vis->win->file->text;
148 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c)) {
149 size_t pos = view_cursors_pos(c);
150 size_t start = text_line_start(txt, pos);
151 size_t end = text_line_end(txt, pos);
152 if (start == pos && start == end) {
153 size_t begin = text_line_begin(txt, pos);
154 size_t len = start - begin;
155 if (len) {
156 text_delete(txt, begin, len);
157 view_cursors_to(c, pos-len);
162 macro_operator_stop(vis);
163 if (!vis->win->parent && vis->action_prev.op == &vis_operators[VIS_OP_MODESWITCH] &&
164 vis->action_prev.count > 1) {
165 /* temporarily disable motion, in something like `5atext`
166 * we should only move the cursor once then insert the text */
167 const Movement *motion = vis->action_prev.movement;
168 if (motion)
169 vis->action_prev.movement = &vis_motions[VIS_MOVE_NOP];
170 /* we already inserted the text once, so temporarily decrease count */
171 vis->action_prev.count--;
172 vis_repeat(vis);
173 vis->action_prev.count++;
174 vis->action_prev.movement = motion;
176 /* make sure we can recover the current state after an editing operation */
177 vis_file_snapshot(vis, vis->win->file);
180 static void vis_mode_operator_input(Vis *vis, const char *str, size_t len) {
181 /* invalid operator */
182 vis_cancel(vis);
183 mode_set(vis, vis->mode_prev);
186 static void vis_mode_visual_enter(Vis *vis, Mode *old) {
187 if (!old->visual) {
188 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c))
189 view_cursors_selection_start(c);
193 static void vis_mode_visual_line_enter(Vis *vis, Mode *old) {
194 if (!old->visual) {
195 for (Cursor *c = view_cursors(vis->win->view); c; c = view_cursors_next(c))
196 view_cursors_selection_start(c);
198 if (!vis->action.op)
199 vis_motion(vis, VIS_MOVE_NOP);
202 static void vis_mode_visual_line_leave(Vis *vis, Mode *new) {
203 if (!new->visual) {
204 window_selection_save(vis->win);
205 view_selections_clear(vis->win->view);
206 } else {
207 view_cursor_to(vis->win->view, view_cursor_get(vis->win->view));
211 static void vis_mode_visual_leave(Vis *vis, Mode *new) {
212 if (!new->visual) {
213 window_selection_save(vis->win);
214 view_selections_clear(vis->win->view);
218 static void vis_mode_insert_replace_enter(Vis *vis, Mode *old) {
219 if (vis->win->parent)
220 return;
221 if (!vis->action.op) {
222 action_reset(&vis->action_prev);
223 vis->action_prev.op = &vis_operators[VIS_OP_MODESWITCH];
224 vis->action_prev.mode = vis->mode->id;
226 macro_operator_record(vis);
229 static void vis_mode_insert_idle(Vis *vis) {
230 vis_file_snapshot(vis, vis->win->file);
233 static void vis_mode_insert_input(Vis *vis, const char *str, size_t len) {
234 vis_insert_key(vis, str, len);
237 static void vis_mode_replace_input(Vis *vis, const char *str, size_t len) {
238 vis_replace_key(vis, str, len);
241 Mode vis_modes[] = {
242 [VIS_MODE_OPERATOR_PENDING] = {
243 .id = VIS_MODE_OPERATOR_PENDING,
244 .name = "OPERATOR-PENDING",
245 .input = vis_mode_operator_input,
246 .help = "",
248 [VIS_MODE_NORMAL] = {
249 .id = VIS_MODE_NORMAL,
250 .name = "NORMAL",
251 .help = "",
252 .enter = vis_mode_normal_enter,
254 [VIS_MODE_VISUAL] = {
255 .id = VIS_MODE_VISUAL,
256 .name = "VISUAL",
257 .status = "VISUAL",
258 .help = "",
259 .enter = vis_mode_visual_enter,
260 .leave = vis_mode_visual_leave,
261 .visual = true,
263 [VIS_MODE_VISUAL_LINE] = {
264 .id = VIS_MODE_VISUAL_LINE,
265 .name = "VISUAL-LINE",
266 .parent = &vis_modes[VIS_MODE_VISUAL],
267 .status = "VISUAL-LINE",
268 .help = "",
269 .enter = vis_mode_visual_line_enter,
270 .leave = vis_mode_visual_line_leave,
271 .visual = true,
273 [VIS_MODE_INSERT] = {
274 .id = VIS_MODE_INSERT,
275 .name = "INSERT",
276 .status = "INSERT",
277 .help = "",
278 .enter = vis_mode_insert_replace_enter,
279 .input = vis_mode_insert_input,
280 .idle = vis_mode_insert_idle,
281 .idle_timeout = 3,
283 [VIS_MODE_REPLACE] = {
284 .id = VIS_MODE_REPLACE,
285 .name = "REPLACE",
286 .parent = &vis_modes[VIS_MODE_INSERT],
287 .status = "REPLACE",
288 .help = "",
289 .enter = vis_mode_insert_replace_enter,
290 .input = vis_mode_replace_input,
291 .idle = vis_mode_insert_idle,
292 .idle_timeout = 3,