Merge branch 'paranthese-typo' of https://github.com/Two-Finger/vis
[vis.git] / vis-operators.c
blob0e8ff30cb154d5894a35787d42becb3246386dbe
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 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;
28 return pos;
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);
38 return c->linewise ? c->pos : c->range.start;
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;
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;
65 size_t len;
66 const char *data = register_slot_get(vis, c->reg, c->reg_slot, &len);
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);
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;
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;
100 return pos;
103 static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) {
104 char spaces[9] = " ";
105 spaces[MIN(vis->tabwidth, LENGTH(spaces) - 1)] = '\0';
106 const char *tab = vis->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;
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;
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);
125 return newpos;
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->tabwidth, tablen;
131 size_t newpos = c->pos;
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);
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);
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;
157 pos = text_line_prev(txt, pos);
158 } while (pos >= c->range.start && pos != prev_pos);
160 return newpos;
163 static size_t op_case_change(Vis *vis, Text *txt, OperatorContext *c) {
164 size_t len = text_range_size(&c->range);
165 char *buf = malloc(len);
166 if (!buf)
167 return c->pos;
168 len = text_bytes_get(txt, c->range.start, len, buf);
169 size_t rem = len;
170 for (char *cur = buf; rem > 0; cur++, rem--) {
171 int ch = (unsigned char)*cur;
172 if (isascii(ch)) {
173 if (c->arg->i == VIS_OP_CASE_SWAP)
174 *cur = islower(ch) ? toupper(ch) : tolower(ch);
175 else if (c->arg->i == VIS_OP_CASE_UPPER)
176 *cur = toupper(ch);
177 else
178 *cur = tolower(ch);
182 text_delete(txt, c->range.start, len);
183 text_insert(txt, c->range.start, buf, len);
184 free(buf);
185 return c->pos;
188 static size_t op_cursor(Vis *vis, Text *txt, OperatorContext *c) {
189 View *view = vis->win->view;
190 Filerange r = text_range_linewise(txt, &c->range);
191 for (size_t line = text_range_line_first(txt, &r); line != EPOS; line = text_range_line_next(txt, &r, line)) {
192 size_t pos;
193 if (c->arg->i == VIS_OP_CURSOR_EOL)
194 pos = text_line_finish(txt, line);
195 else
196 pos = text_line_start(txt, line);
197 view_selections_new_force(view, pos);
199 return EPOS;
202 static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) {
203 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
204 Mark mark = EMARK;
206 /* if operator and range are both linewise, skip last line break */
207 if (c->linewise && text_range_is_linewise(txt, &c->range)) {
208 size_t line_prev = text_line_prev(txt, pos);
209 size_t line_prev_prev = text_line_prev(txt, line_prev);
210 if (line_prev_prev >= c->range.start)
211 pos = line_prev;
214 size_t len = c->arg->s ? strlen(c->arg->s) : 0;
216 do {
217 prev_pos = pos;
218 size_t end = text_line_start(txt, pos);
219 pos = text_line_prev(txt, end);
220 if (pos < c->range.start || end <= pos)
221 break;
222 text_delete(txt, pos, end - pos);
223 char prev, next;
224 if (text_byte_get(txt, pos-1, &prev) && !isspace((unsigned char)prev) &&
225 text_byte_get(txt, pos, &next) && next != '\n')
226 text_insert(txt, pos, c->arg->s, len);
227 if (mark == EMARK)
228 mark = text_mark_set(txt, pos);
229 } while (pos != prev_pos);
231 size_t newpos = text_mark_get(txt, mark);
232 return newpos != EPOS ? newpos : c->range.start;
235 static size_t op_modeswitch(Vis *vis, Text *txt, OperatorContext *c) {
236 return c->newpos != EPOS ? c->newpos : c->pos;
239 static size_t op_replace(Vis *vis, Text *txt, OperatorContext *c) {
240 size_t count = 0;
241 Iterator it = text_iterator_get(txt, c->range.start);
242 while (it. pos < c->range.end && text_iterator_char_next(&it, NULL))
243 count++;
244 op_delete(vis, txt, c);
245 size_t pos = c->range.start;
246 for (size_t len = strlen(c->arg->s); count > 0; pos += len, count--)
247 text_insert(txt, pos, c->arg->s, len);
248 return c->range.start;
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_SHIFT_LEFT:
293 case VIS_OP_SHIFT_RIGHT:
294 vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
295 break;
296 case VIS_OP_REPLACE:
298 Macro *macro = macro_get(vis, VIS_REG_DOT);
299 macro_reset(macro);
300 macro_append(macro, va_arg(ap, char*));
301 vis->action.arg.s = macro->data;
302 break;
304 case VIS_OP_DELETE:
306 enum VisMode mode = vis_mode_get(vis);
307 enum VisRegister reg = vis_register_used(vis);
308 if (reg == VIS_REG_DEFAULT && (mode == VIS_MODE_INSERT || mode == VIS_MODE_REPLACE))
309 vis_register(vis, VIS_REG_BLACKHOLE);
310 break;
312 default:
313 break;
316 const Operator *op = NULL;
317 if (id < LENGTH(vis_operators))
318 op = &vis_operators[id];
319 else
320 op = array_get_ptr(&vis->operators, id - VIS_OP_LAST);
322 if (!op)
323 goto err;
325 if (vis->mode->visual) {
326 vis->action.op = op;
327 vis_do(vis);
328 goto out;
331 /* switch to operator mode inorder to make operator options and
332 * text-object available */
333 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
334 if (vis->action.op == op) {
335 /* hacky way to handle double operators i.e. things like
336 * dd, yy etc where the second char isn't a movement */
337 vis->action.type = LINEWISE;
338 vis_motion(vis, VIS_MOVE_LINE_NEXT);
339 } else {
340 vis->action.op = op;
343 /* put is not a real operator, does not need a range to operate on */
344 if (id == VIS_OP_PUT_AFTER)
345 vis_motion(vis, VIS_MOVE_NOP);
347 out:
348 va_end(ap);
349 return true;
350 err:
351 va_end(ap);
352 return false;
355 const Operator vis_operators[] = {
356 [VIS_OP_DELETE] = { op_delete },
357 [VIS_OP_CHANGE] = { op_change },
358 [VIS_OP_YANK] = { op_yank },
359 [VIS_OP_PUT_AFTER] = { op_put },
360 [VIS_OP_SHIFT_RIGHT] = { op_shift_right },
361 [VIS_OP_SHIFT_LEFT] = { op_shift_left },
362 [VIS_OP_CASE_SWAP] = { op_case_change },
363 [VIS_OP_JOIN] = { op_join },
364 [VIS_OP_MODESWITCH] = { op_modeswitch },
365 [VIS_OP_REPLACE] = { op_replace },
366 [VIS_OP_CURSOR_SOL] = { op_cursor },