vis: implement intersection of selections
[vis.git] / vis-operators.c
blob38dfbae232897b244af8a32ddaf39f77f905dd20
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"
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;
19 static size_t op_change(Vis *vis, Text *txt, OperatorContext *c) {
20 op_delete(vis, txt, c);
21 size_t pos = c->range.start;
22 if (c->linewise)
23 pos = vis_text_insert_nl(vis, txt, pos > 0 ? pos-1 : pos);
24 return pos;
27 static size_t op_yank(Vis *vis, Text *txt, OperatorContext *c) {
28 c->reg->linewise = c->linewise;
29 register_slot_put_range(vis, c->reg, c->reg_slot, txt, &c->range);
30 if (c->reg == &vis->registers[VIS_REG_DEFAULT]) {
31 vis->registers[VIS_REG_ZERO].linewise = c->reg->linewise;
32 register_slot_put_range(vis, &vis->registers[VIS_REG_ZERO], c->reg_slot, txt, &c->range);
34 return c->linewise ? c->pos : c->range.start;
37 static size_t op_put(Vis *vis, Text *txt, OperatorContext *c) {
38 char b;
39 size_t pos = c->pos;
40 bool sel = text_range_size(&c->range) > 0;
41 bool sel_linewise = sel && text_range_is_linewise(txt, &c->range);
42 if (sel) {
43 text_delete_range(txt, &c->range);
44 pos = c->pos = c->range.start;
46 switch (c->arg->i) {
47 case VIS_OP_PUT_AFTER:
48 case VIS_OP_PUT_AFTER_END:
49 if (c->reg->linewise && !sel_linewise)
50 pos = text_line_next(txt, pos);
51 else if (!sel && text_byte_get(txt, pos, &b) && b != '\n')
52 pos = text_char_next(txt, pos);
53 break;
54 case VIS_OP_PUT_BEFORE:
55 case VIS_OP_PUT_BEFORE_END:
56 if (c->reg->linewise)
57 pos = text_line_begin(txt, pos);
58 break;
61 size_t len;
62 const char *data = register_slot_get(vis, c->reg, c->reg_slot, &len);
64 for (int i = 0; i < c->count; i++) {
65 char nl;
66 if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
67 pos += text_insert(txt, pos, "\n", 1);
68 text_insert(txt, pos, data, len);
69 pos += len;
70 if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
71 pos += text_insert(txt, pos, "\n", 1);
74 if (c->reg->linewise) {
75 switch (c->arg->i) {
76 case VIS_OP_PUT_BEFORE_END:
77 case VIS_OP_PUT_AFTER_END:
78 pos = text_line_start(txt, pos);
79 break;
80 case VIS_OP_PUT_AFTER:
81 pos = text_line_start(txt, text_line_next(txt, c->pos));
82 break;
83 case VIS_OP_PUT_BEFORE:
84 pos = text_line_start(txt, c->pos);
85 break;
87 } else {
88 switch (c->arg->i) {
89 case VIS_OP_PUT_AFTER:
90 case VIS_OP_PUT_BEFORE:
91 pos = text_char_prev(txt, pos);
92 break;
96 return pos;
99 static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) {
100 char spaces[9] = " ";
101 spaces[MIN(vis->tabwidth, LENGTH(spaces) - 1)] = '\0';
102 const char *tab = vis->expandtab ? spaces : "\t";
103 size_t tablen = strlen(tab);
104 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
105 size_t newpos = c->pos;
107 /* if range ends at the begin of a line, skip line break */
108 if (pos == c->range.end)
109 pos = text_line_prev(txt, pos);
110 bool multiple_lines = text_line_prev(txt, pos) >= c->range.start;
112 do {
113 size_t end = text_line_end(txt, pos);
114 prev_pos = pos = text_line_begin(txt, end);
115 if ((!multiple_lines || pos != end) &&
116 text_insert(txt, pos, tab, tablen) && pos <= c->pos)
117 newpos += tablen;
118 pos = text_line_prev(txt, pos);
119 } while (pos >= c->range.start && pos != prev_pos);
121 return newpos;
124 static size_t op_shift_left(Vis *vis, Text *txt, OperatorContext *c) {
125 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
126 size_t tabwidth = vis->tabwidth, tablen;
127 size_t newpos = c->pos;
129 /* if range ends at the begin of a line, skip line break */
130 if (pos == c->range.end)
131 pos = text_line_prev(txt, pos);
133 do {
134 char b;
135 size_t len = 0;
136 prev_pos = pos = text_line_begin(txt, pos);
137 Iterator it = text_iterator_get(txt, pos);
138 if (text_iterator_byte_get(&it, &b) && b == '\t') {
139 len = 1;
140 } else {
141 for (len = 0; text_iterator_byte_get(&it, &b) && b == ' '; len++)
142 text_iterator_byte_next(&it, NULL);
144 tablen = MIN(len, tabwidth);
145 if (text_delete(txt, pos, tablen) && pos < c->pos) {
146 size_t delta = c->pos - pos;
147 if (delta > tablen)
148 delta = tablen;
149 if (delta > newpos)
150 delta = newpos;
151 newpos -= delta;
153 pos = text_line_prev(txt, pos);
154 } while (pos >= c->range.start && pos != prev_pos);
156 return newpos;
159 static size_t op_case_change(Vis *vis, Text *txt, OperatorContext *c) {
160 size_t len = text_range_size(&c->range);
161 char *buf = malloc(len);
162 if (!buf)
163 return c->pos;
164 len = text_bytes_get(txt, c->range.start, len, buf);
165 size_t rem = len;
166 for (char *cur = buf; rem > 0; cur++, rem--) {
167 int ch = (unsigned char)*cur;
168 if (isascii(ch)) {
169 if (c->arg->i == VIS_OP_CASE_SWAP)
170 *cur = islower(ch) ? toupper(ch) : tolower(ch);
171 else if (c->arg->i == VIS_OP_CASE_UPPER)
172 *cur = toupper(ch);
173 else
174 *cur = tolower(ch);
178 text_delete(txt, c->range.start, len);
179 text_insert(txt, c->range.start, buf, len);
180 free(buf);
181 return c->pos;
184 static size_t op_cursor(Vis *vis, Text *txt, OperatorContext *c) {
185 View *view = vis->win->view;
186 Filerange r = text_range_linewise(txt, &c->range);
187 for (size_t line = text_range_line_first(txt, &r); line != EPOS; line = text_range_line_next(txt, &r, line)) {
188 size_t pos;
189 if (c->arg->i == VIS_OP_CURSOR_EOL)
190 pos = text_line_finish(txt, line);
191 else
192 pos = text_line_start(txt, line);
193 view_selections_new_force(view, pos);
195 return EPOS;
198 static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) {
199 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
200 Mark mark = EMARK;
202 /* if operator and range are both linewise, skip last line break */
203 if (c->linewise && text_range_is_linewise(txt, &c->range)) {
204 size_t line_prev = text_line_prev(txt, pos);
205 size_t line_prev_prev = text_line_prev(txt, line_prev);
206 if (line_prev_prev >= c->range.start)
207 pos = line_prev;
210 size_t len = c->arg->s ? strlen(c->arg->s) : 0;
212 do {
213 prev_pos = pos;
214 size_t end = text_line_start(txt, pos);
215 pos = text_line_prev(txt, end);
216 if (pos < c->range.start || end <= pos)
217 break;
218 text_delete(txt, pos, end - pos);
219 char prev, next;
220 if (text_byte_get(txt, pos-1, &prev) && !isspace((unsigned char)prev) &&
221 text_byte_get(txt, pos, &next) && next != '\n')
222 text_insert(txt, pos, c->arg->s, len);
223 if (mark == EMARK)
224 mark = text_mark_set(txt, pos);
225 } while (pos != prev_pos);
227 size_t newpos = text_mark_get(txt, mark);
228 return newpos != EPOS ? newpos : c->range.start;
231 static size_t op_modeswitch(Vis *vis, Text *txt, OperatorContext *c) {
232 return c->newpos != EPOS ? c->newpos : c->pos;
235 static size_t op_replace(Vis *vis, Text *txt, OperatorContext *c) {
236 size_t count = 0;
237 Iterator it = text_iterator_get(txt, c->range.start);
238 while (it. pos < c->range.end && text_iterator_char_next(&it, NULL))
239 count++;
240 op_delete(vis, txt, c);
241 size_t pos = c->range.start;
242 for (size_t len = strlen(c->arg->s); count > 0; pos += len, count--)
243 text_insert(txt, pos, c->arg->s, len);
244 return c->range.start;
247 static size_t op_filter(Vis *vis, Text *txt, OperatorContext *c) {
248 return text_size(txt) + 1; /* do not change cursor position, would destroy selection */
251 int vis_operator_register(Vis *vis, VisOperatorFunction *func, void *context) {
252 Operator *op = calloc(1, sizeof *op);
253 if (!op)
254 return -1;
255 op->func = func;
256 op->context = context;
257 if (array_add_ptr(&vis->operators, op))
258 return VIS_OP_LAST + array_length(&vis->operators) - 1;
259 free(op);
260 return -1;
263 bool vis_operator(Vis *vis, enum VisOperator id, ...) {
264 va_list ap;
265 va_start(ap, id);
267 switch (id) {
268 case VIS_OP_MODESWITCH:
269 vis->action.mode = va_arg(ap, int);
270 break;
271 case VIS_OP_CASE_LOWER:
272 case VIS_OP_CASE_UPPER:
273 case VIS_OP_CASE_SWAP:
274 vis->action.arg.i = id;
275 id = VIS_OP_CASE_SWAP;
276 break;
277 case VIS_OP_CURSOR_SOL:
278 case VIS_OP_CURSOR_EOL:
279 vis->action.arg.i = id;
280 id = VIS_OP_CURSOR_SOL;
281 break;
282 case VIS_OP_PUT_AFTER:
283 case VIS_OP_PUT_AFTER_END:
284 case VIS_OP_PUT_BEFORE:
285 case VIS_OP_PUT_BEFORE_END:
286 vis->action.arg.i = id;
287 id = VIS_OP_PUT_AFTER;
288 break;
289 case VIS_OP_JOIN:
290 vis->action.arg.s = va_arg(ap, char*);
291 break;
292 case VIS_OP_FILTER:
293 vis->action.arg.s = va_arg(ap, char*);
294 /* fall through */
295 case VIS_OP_SHIFT_LEFT:
296 case VIS_OP_SHIFT_RIGHT:
297 vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
298 break;
299 case VIS_OP_REPLACE:
301 Macro *macro = macro_get(vis, VIS_REG_DOT);
302 macro_reset(macro);
303 macro_append(macro, va_arg(ap, char*));
304 vis->action.arg.s = macro->data;
305 break;
307 default:
308 break;
311 const Operator *op = NULL;
312 if (id < LENGTH(vis_operators))
313 op = &vis_operators[id];
314 else
315 op = array_get_ptr(&vis->operators, id - VIS_OP_LAST);
317 if (!op)
318 goto err;
320 if (vis->mode->visual) {
321 vis->action.op = op;
322 vis_do(vis);
323 goto out;
326 /* switch to operator mode inorder to make operator options and
327 * text-object available */
328 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
329 if (vis->action.op == op) {
330 /* hacky way to handle double operators i.e. things like
331 * dd, yy etc where the second char isn't a movement */
332 vis->action.type = LINEWISE;
333 vis_motion(vis, VIS_MOVE_LINE_NEXT);
334 } else {
335 vis->action.op = op;
338 /* put is not a real operator, does not need a range to operate on */
339 if (id == VIS_OP_PUT_AFTER)
340 vis_motion(vis, VIS_MOVE_NOP);
342 out:
343 va_end(ap);
344 return true;
345 err:
346 va_end(ap);
347 return false;
350 const Operator vis_operators[] = {
351 [VIS_OP_DELETE] = { op_delete },
352 [VIS_OP_CHANGE] = { op_change },
353 [VIS_OP_YANK] = { op_yank },
354 [VIS_OP_PUT_AFTER] = { op_put },
355 [VIS_OP_SHIFT_RIGHT] = { op_shift_right },
356 [VIS_OP_SHIFT_LEFT] = { op_shift_left },
357 [VIS_OP_CASE_SWAP] = { op_case_change },
358 [VIS_OP_JOIN] = { op_join },
359 [VIS_OP_MODESWITCH] = { op_modeswitch },
360 [VIS_OP_REPLACE] = { op_replace },
361 [VIS_OP_CURSOR_SOL] = { op_cursor },
362 [VIS_OP_FILTER] = { op_filter },