vis

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

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

vis-operators.c

(9445B)


      1 #include <string.h>
      2 #include <ctype.h>
      3 #include "vis-core.h"
      4 #include "text-motions.h"
      5 #include "text-objects.h"
      6 #include "text-util.h"
      7 #include "util.h"
      8 
      9 static size_t op_delete(Vis *vis, Text *txt, OperatorContext *c) {
     10 	c->reg->linewise = c->linewise;
     11 	register_slot_put_range(vis, c->reg, c->reg_slot, txt, &c->range);
     12 	text_delete_range(txt, &c->range);
     13 	size_t pos = c->range.start;
     14 	if (c->linewise && pos == text_size(txt))
     15 		pos = text_line_begin(txt, text_line_prev(txt, pos));
     16 	return pos;
     17 }
     18 
     19 static size_t op_change(Vis *vis, Text *txt, OperatorContext *c) {
     20 	bool linewise = c->linewise || text_range_is_linewise(txt, &c->range);
     21 	op_delete(vis, txt, c);
     22 	size_t pos = c->range.start;
     23 	if (linewise) {
     24 		size_t newpos = vis_text_insert_nl(vis, txt, pos > 0 ? pos-1 : pos);
     25 		if (pos > 0)
     26 			pos = newpos;
     27 	}
     28 	return pos;
     29 }
     30 
     31 static size_t op_yank(Vis *vis, Text *txt, OperatorContext *c) {
     32 	c->reg->linewise = c->linewise;
     33 	register_slot_put_range(vis, c->reg, c->reg_slot, txt, &c->range);
     34 	if (c->reg == &vis->registers[VIS_REG_DEFAULT]) {
     35 		vis->registers[VIS_REG_ZERO].linewise = c->reg->linewise;
     36 		register_slot_put_range(vis, &vis->registers[VIS_REG_ZERO], c->reg_slot, txt, &c->range);
     37 	}
     38 	return c->linewise ? c->pos : c->range.start;
     39 }
     40 
     41 static size_t op_put(Vis *vis, Text *txt, OperatorContext *c) {
     42 	char b;
     43 	size_t pos = c->pos;
     44 	bool sel = text_range_size(&c->range) > 0;
     45 	bool sel_linewise = sel && text_range_is_linewise(txt, &c->range);
     46 	if (sel) {
     47 		text_delete_range(txt, &c->range);
     48 		pos = c->pos = c->range.start;
     49 	}
     50 	switch (c->arg->i) {
     51 	case VIS_OP_PUT_AFTER:
     52 	case VIS_OP_PUT_AFTER_END:
     53 		if (c->reg->linewise && !sel_linewise)
     54 			pos = text_line_next(txt, pos);
     55 		else if (!sel && text_byte_get(txt, pos, &b) && b != '\n')
     56 			pos = text_char_next(txt, pos);
     57 		break;
     58 	case VIS_OP_PUT_BEFORE:
     59 	case VIS_OP_PUT_BEFORE_END:
     60 		if (c->reg->linewise)
     61 			pos = text_line_begin(txt, pos);
     62 		break;
     63 	}
     64 
     65 	size_t len;
     66 	const char *data = register_slot_get(vis, c->reg, c->reg_slot, &len);
     67 
     68 	for (int i = 0; i < c->count; i++) {
     69 		char nl;
     70 		if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
     71 			pos += text_insert(txt, pos, "\n", 1);
     72 		text_insert(txt, pos, data, len);
     73 		pos += len;
     74 		if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
     75 			pos += text_insert(txt, pos, "\n", 1);
     76 	}
     77 
     78 	if (c->reg->linewise) {
     79 		switch (c->arg->i) {
     80 		case VIS_OP_PUT_BEFORE_END:
     81 		case VIS_OP_PUT_AFTER_END:
     82 			pos = text_line_start(txt, pos);
     83 			break;
     84 		case VIS_OP_PUT_AFTER:
     85 			pos = text_line_start(txt, text_line_next(txt, c->pos));
     86 			break;
     87 		case VIS_OP_PUT_BEFORE:
     88 			pos = text_line_start(txt, c->pos);
     89 			break;
     90 		}
     91 	} else {
     92 		switch (c->arg->i) {
     93 		case VIS_OP_PUT_AFTER:
     94 		case VIS_OP_PUT_BEFORE:
     95 			pos = text_char_prev(txt, pos);
     96 			break;
     97 		}
     98 	}
     99 
    100 	return pos;
    101 }
    102 
    103 static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) {
    104 	char spaces[9] = "         ";
    105 	spaces[MIN(vis->win->view.tabwidth, LENGTH(spaces) - 1)] = '\0';
    106 	const char *tab = vis->win->expandtab ? spaces : "\t";
    107 	size_t tablen = strlen(tab);
    108 	size_t pos = text_line_begin(txt, c->range.end), prev_pos;
    109 	size_t newpos = c->pos;
    110 
    111 	/* if range ends at the begin of a line, skip line break */
    112 	if (pos == c->range.end)
    113 		pos = text_line_prev(txt, pos);
    114 	bool multiple_lines = text_line_prev(txt, pos) >= c->range.start;
    115 
    116 	do {
    117 		size_t end = text_line_end(txt, pos);
    118 		prev_pos = pos = text_line_begin(txt, end);
    119 		if ((!multiple_lines || pos != end) &&
    120 		    text_insert(txt, pos, tab, tablen) && pos <= c->pos)
    121 			newpos += tablen;
    122 		pos = text_line_prev(txt, pos);
    123 	}  while (pos >= c->range.start && pos != prev_pos);
    124 
    125 	return newpos;
    126 }
    127 
    128 static size_t op_shift_left(Vis *vis, Text *txt, OperatorContext *c) {
    129 	size_t pos = text_line_begin(txt, c->range.end), prev_pos;
    130 	size_t tabwidth = vis->win->view.tabwidth, tablen;
    131 	size_t newpos = c->pos;
    132 
    133 	/* if range ends at the begin of a line, skip line break */
    134 	if (pos == c->range.end)
    135 		pos = text_line_prev(txt, pos);
    136 
    137 	do {
    138 		char b;
    139 		size_t len = 0;
    140 		prev_pos = pos = text_line_begin(txt, pos);
    141 		Iterator it = text_iterator_get(txt, pos);
    142 		if (text_iterator_byte_get(&it, &b) && b == '\t') {
    143 			len = 1;
    144 		} else {
    145 			for (len = 0; text_iterator_byte_get(&it, &b) && b == ' '; len++)
    146 				text_iterator_byte_next(&it, NULL);
    147 		}
    148 		tablen = MIN(len, tabwidth);
    149 		if (text_delete(txt, pos, tablen) && pos < c->pos) {
    150 			size_t delta = c->pos - pos;
    151 			if (delta > tablen)
    152 				delta = tablen;
    153 			if (delta > newpos)
    154 				delta = newpos;
    155 			newpos -= delta;
    156 		}
    157 		pos = text_line_prev(txt, pos);
    158 	}  while (pos >= c->range.start && pos != prev_pos);
    159 
    160 	return newpos;
    161 }
    162 
    163 static size_t op_cursor(Vis *vis, Text *txt, OperatorContext *c) {
    164 	Filerange r = text_range_linewise(txt, &c->range);
    165 	for (size_t line = text_range_line_first(txt, &r); line != EPOS; line = text_range_line_next(txt, &r, line)) {
    166 		size_t pos;
    167 		if (c->arg->i == VIS_OP_CURSOR_EOL)
    168 			pos = text_line_finish(txt, line);
    169 		else
    170 			pos = text_line_start(txt, line);
    171 		view_selections_new_force(&vis->win->view, pos);
    172 	}
    173 	return EPOS;
    174 }
    175 
    176 static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) {
    177 	size_t pos = text_line_begin(txt, c->range.end), prev_pos;
    178 	Mark mark = EMARK;
    179 
    180 	/* if operator and range are both linewise, skip last line break */
    181 	if (c->linewise && text_range_is_linewise(txt, &c->range)) {
    182 		size_t line_prev = text_line_prev(txt, pos);
    183 		size_t line_prev_prev = text_line_prev(txt, line_prev);
    184 		if (line_prev_prev >= c->range.start)
    185 			pos = line_prev;
    186 	}
    187 
    188 	size_t len = c->arg->s ? strlen(c->arg->s) : 0;
    189 
    190 	do {
    191 		prev_pos = pos;
    192 		size_t end = text_line_start(txt, pos);
    193 		pos = text_line_prev(txt, end);
    194 		if (pos < c->range.start || end <= pos)
    195 			break;
    196 		text_delete(txt, pos, end - pos);
    197 		char prev, next;
    198 		if (text_byte_get(txt, pos-1, &prev) && !isspace((unsigned char)prev) &&
    199 		    text_byte_get(txt, pos, &next) && next != '\n')
    200 			text_insert(txt, pos, c->arg->s, len);
    201 		if (mark == EMARK)
    202 			mark = text_mark_set(txt, pos);
    203 	} while (pos != prev_pos);
    204 
    205 	size_t newpos = text_mark_get(txt, mark);
    206 	return newpos != EPOS ? newpos : c->range.start;
    207 }
    208 
    209 static size_t op_modeswitch(Vis *vis, Text *txt, OperatorContext *c) {
    210 	return c->newpos != EPOS ? c->newpos : c->pos;
    211 }
    212 
    213 static size_t op_replace(Vis *vis, Text *txt, OperatorContext *c) {
    214 	size_t count = 0;
    215 	Iterator it = text_iterator_get(txt, c->range.start);
    216 	while (it. pos < c->range.end && text_iterator_char_next(&it, NULL))
    217 		count++;
    218 	op_delete(vis, txt, c);
    219 	size_t pos = c->range.start;
    220 	for (size_t len = strlen(c->arg->s); count > 0; pos += len, count--)
    221 		text_insert(txt, pos, c->arg->s, len);
    222 	return c->range.start;
    223 }
    224 
    225 int vis_operator_register(Vis *vis, VisOperatorFunction *func, void *context) {
    226 	Operator *op = calloc(1, sizeof *op);
    227 	if (!op)
    228 		return -1;
    229 	op->func = func;
    230 	op->context = context;
    231 	if (array_add_ptr(&vis->operators, op))
    232 		return VIS_OP_LAST + vis->operators.len - 1;
    233 	free(op);
    234 	return -1;
    235 }
    236 
    237 bool vis_operator(Vis *vis, enum VisOperator id, ...) {
    238 	va_list ap;
    239 	va_start(ap, id);
    240 
    241 	switch (id) {
    242 	case VIS_OP_MODESWITCH:
    243 		vis->action.mode = va_arg(ap, int);
    244 		break;
    245 	case VIS_OP_CURSOR_SOL:
    246 	case VIS_OP_CURSOR_EOL:
    247 		vis->action.arg.i = id;
    248 		id = VIS_OP_CURSOR_SOL;
    249 		break;
    250 	case VIS_OP_PUT_AFTER:
    251 	case VIS_OP_PUT_AFTER_END:
    252 	case VIS_OP_PUT_BEFORE:
    253 	case VIS_OP_PUT_BEFORE_END:
    254 		vis->action.arg.i = id;
    255 		id = VIS_OP_PUT_AFTER;
    256 		break;
    257 	case VIS_OP_JOIN:
    258 		vis->action.arg.s = va_arg(ap, char*);
    259 		break;
    260 	case VIS_OP_SHIFT_LEFT:
    261 	case VIS_OP_SHIFT_RIGHT:
    262 		vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
    263 		break;
    264 	case VIS_OP_REPLACE:
    265 	{
    266 		Macro *macro = macro_get(vis, VIS_REG_DOT);
    267 		macro->len   = 0;
    268 		macro_append(macro, va_arg(ap, char*));
    269 		vis->action.arg.s = macro->data;
    270 		break;
    271 	}
    272 	case VIS_OP_DELETE:
    273 	{
    274 		enum VisMode mode = vis->mode->id;
    275 		enum VisRegister reg = vis_register_used(vis);
    276 		if (reg == VIS_REG_DEFAULT && (mode == VIS_MODE_INSERT || mode == VIS_MODE_REPLACE))
    277 			vis_register(vis, VIS_REG_BLACKHOLE);
    278 		break;
    279 	}
    280 	default:
    281 		break;
    282 	}
    283 
    284 	const Operator *op = NULL;
    285 	if (id < LENGTH(vis_operators))
    286 		op = &vis_operators[id];
    287 	else
    288 		op = array_get_ptr(&vis->operators, id - VIS_OP_LAST);
    289 
    290 	if (!op)
    291 		goto err;
    292 
    293 	if (vis->mode->visual) {
    294 		vis->action.op = op;
    295 		vis_do(vis);
    296 		goto out;
    297 	}
    298 
    299 	/* switch to operator mode inorder to make operator options and
    300 	 * text-object available */
    301 	vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
    302 	if (vis->action.op == op) {
    303 		/* hacky way to handle double operators i.e. things like
    304 		 * dd, yy etc where the second char isn't a movement */
    305 		vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
    306 		vis_motion(vis, VIS_MOVE_LINE_NEXT);
    307 	} else {
    308 		vis->action.op = op;
    309 	}
    310 
    311 	/* put is not a real operator, does not need a range to operate on */
    312 	if (id == VIS_OP_PUT_AFTER)
    313 		vis_motion(vis, VIS_MOVE_NOP);
    314 
    315 out:
    316 	va_end(ap);
    317 	return true;
    318 err:
    319 	va_end(ap);
    320 	return false;
    321 }
    322 
    323 const Operator vis_operators[] = {
    324 	[VIS_OP_DELETE]      = { op_delete      },
    325 	[VIS_OP_CHANGE]      = { op_change      },
    326 	[VIS_OP_YANK]        = { op_yank        },
    327 	[VIS_OP_PUT_AFTER]   = { op_put         },
    328 	[VIS_OP_SHIFT_RIGHT] = { op_shift_right },
    329 	[VIS_OP_SHIFT_LEFT]  = { op_shift_left  },
    330 	[VIS_OP_JOIN]        = { op_join        },
    331 	[VIS_OP_MODESWITCH]  = { op_modeswitch  },
    332 	[VIS_OP_REPLACE]     = { op_replace     },
    333 	[VIS_OP_CURSOR_SOL]  = { op_cursor      },
    334 };