vis

a vi-like editor based on Plan 9's structural regular expressions

git clone https://9o.is/git/vis.git

vis-modes.c

(8285B)


      1 #include <string.h>
      2 #include <strings.h>
      3 #include "vis-core.h"
      4 #include "text-motions.h"
      5 #include "util.h"
      6 
      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);
     13 }
     14 
     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;
     33 }
     34 
     35 void vis_action_free(Vis *vis, KeyAction *action) {
     36 	if (!action)
     37 		return;
     38 	size_t len = vis->actions_user.len;
     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;
     44 		}
     45 	}
     46 }
     47 
     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;
     54 }
     55 
     56 void vis_binding_free(Vis *vis, KeyBinding *binding) {
     57 	if (!binding)
     58 		return;
     59 	size_t len = vis->bindings.len;
     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;
     69 		}
     70 	}
     71 }
     72 
     73 Mode *mode_get(Vis *vis, enum VisMode mode) {
     74 	if (mode < LENGTH(vis_modes))
     75 		return &vis_modes[mode];
     76 	return NULL;
     77 }
     78 
     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);
     89 }
     90 
     91 void vis_mode_switch(Vis *vis, enum VisMode mode) {
     92 	if (mode < LENGTH(vis_modes))
     93 		mode_set(vis, &vis_modes[mode]);
     94 }
     95 
     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;
    101 	}
    102 	return VIS_MODE_INVALID;
    103 }
    104 
    105 enum VisMode vis_mode_get(Vis *vis) {
    106 	return vis->mode->id;
    107 }
    108 
    109 static bool mode_unmap(Mode *mode, const char *key) {
    110 	return mode && mode->bindings && map_delete(mode->bindings, key);
    111 }
    112 
    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);
    115 }
    116 
    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);
    119 }
    120 
    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);
    131 }
    132 
    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);
    135 }
    136 
    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);
    139 }
    140 
    141 /** mode switching event handlers */
    142 
    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);
    161 				}
    162 			}
    163 		}
    164 	}
    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;
    178 	}
    179 	/* make sure we can recover the current state after an editing operation */
    180 	vis_file_snapshot(vis, win->file);
    181 }
    182 
    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);
    187 }
    188 
    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 			s->anchored = true;
    194 	}
    195 }
    196 
    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 			s->anchored = true;
    202 	}
    203 	if (!vis->action.op)
    204 		vis_motion(vis, VIS_MOVE_NOP);
    205 }
    206 
    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_cursors_to(win->view.selection, view_cursor_get(&win->view));
    217 	}
    218 }
    219 
    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);
    226 	}
    227 }
    228 
    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;
    236 	}
    237 	macro_operator_record(vis);
    238 }
    239 
    240 static void vis_mode_insert_idle(Vis *vis) {
    241 	Win *win = vis->win;
    242 	if (win)
    243 		vis_file_snapshot(vis, win->file);
    244 }
    245 
    246 static void vis_mode_insert_input(Vis *vis, const char *str, size_t len) {
    247 	vis_insert_key(vis, str, len);
    248 }
    249 
    250 static void vis_mode_replace_input(Vis *vis, const char *str, size_t len) {
    251 	vis_replace_key(vis, str, len);
    252 }
    253 
    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 = "",
    260 	},
    261 	[VIS_MODE_NORMAL] = {
    262 		.id = VIS_MODE_NORMAL,
    263 		.name = "NORMAL",
    264 		.help = "",
    265 		.enter = vis_mode_normal_enter,
    266 	},
    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,
    275 	},
    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,
    285 	},
    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,
    295 	},
    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,
    306 	},
    307 };
    308