filetype: Set "groovy" for Jenkinsfile
[vis.git] / vis-operators.c
blob6523a8533f5b1e1219348eefdd2d7e634f83253b
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_cursor(Vis *vis, Text *txt, OperatorContext *c) {
164 View *view = vis->win->view;
165 Filerange r = text_range_linewise(txt, &c->range);
166 for (size_t line = text_range_line_first(txt, &r); line != EPOS; line = text_range_line_next(txt, &r, line)) {
167 size_t pos;
168 if (c->arg->i == VIS_OP_CURSOR_EOL)
169 pos = text_line_finish(txt, line);
170 else
171 pos = text_line_start(txt, line);
172 view_selections_new_force(view, pos);
174 return EPOS;
177 static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) {
178 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
179 Mark mark = EMARK;
181 /* if operator and range are both linewise, skip last line break */
182 if (c->linewise && text_range_is_linewise(txt, &c->range)) {
183 size_t line_prev = text_line_prev(txt, pos);
184 size_t line_prev_prev = text_line_prev(txt, line_prev);
185 if (line_prev_prev >= c->range.start)
186 pos = line_prev;
189 size_t len = c->arg->s ? strlen(c->arg->s) : 0;
191 do {
192 prev_pos = pos;
193 size_t end = text_line_start(txt, pos);
194 pos = text_line_prev(txt, end);
195 if (pos < c->range.start || end <= pos)
196 break;
197 text_delete(txt, pos, end - pos);
198 char prev, next;
199 if (text_byte_get(txt, pos-1, &prev) && !isspace((unsigned char)prev) &&
200 text_byte_get(txt, pos, &next) && next != '\n')
201 text_insert(txt, pos, c->arg->s, len);
202 if (mark == EMARK)
203 mark = text_mark_set(txt, pos);
204 } while (pos != prev_pos);
206 size_t newpos = text_mark_get(txt, mark);
207 return newpos != EPOS ? newpos : c->range.start;
210 static size_t op_modeswitch(Vis *vis, Text *txt, OperatorContext *c) {
211 return c->newpos != EPOS ? c->newpos : c->pos;
214 static size_t op_replace(Vis *vis, Text *txt, OperatorContext *c) {
215 size_t count = 0;
216 Iterator it = text_iterator_get(txt, c->range.start);
217 while (it. pos < c->range.end && text_iterator_char_next(&it, NULL))
218 count++;
219 op_delete(vis, txt, c);
220 size_t pos = c->range.start;
221 for (size_t len = strlen(c->arg->s); count > 0; pos += len, count--)
222 text_insert(txt, pos, c->arg->s, len);
223 return c->range.start;
226 int vis_operator_register(Vis *vis, VisOperatorFunction *func, void *context) {
227 Operator *op = calloc(1, sizeof *op);
228 if (!op)
229 return -1;
230 op->func = func;
231 op->context = context;
232 if (array_add_ptr(&vis->operators, op))
233 return VIS_OP_LAST + array_length(&vis->operators) - 1;
234 free(op);
235 return -1;
238 bool vis_operator(Vis *vis, enum VisOperator id, ...) {
239 va_list ap;
240 va_start(ap, id);
242 switch (id) {
243 case VIS_OP_MODESWITCH:
244 vis->action.mode = va_arg(ap, int);
245 break;
246 case VIS_OP_CURSOR_SOL:
247 case VIS_OP_CURSOR_EOL:
248 vis->action.arg.i = id;
249 id = VIS_OP_CURSOR_SOL;
250 break;
251 case VIS_OP_PUT_AFTER:
252 case VIS_OP_PUT_AFTER_END:
253 case VIS_OP_PUT_BEFORE:
254 case VIS_OP_PUT_BEFORE_END:
255 vis->action.arg.i = id;
256 id = VIS_OP_PUT_AFTER;
257 break;
258 case VIS_OP_JOIN:
259 vis->action.arg.s = va_arg(ap, char*);
260 break;
261 case VIS_OP_SHIFT_LEFT:
262 case VIS_OP_SHIFT_RIGHT:
263 vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
264 break;
265 case VIS_OP_REPLACE:
267 Macro *macro = macro_get(vis, VIS_REG_DOT);
268 macro_reset(macro);
269 macro_append(macro, va_arg(ap, char*));
270 vis->action.arg.s = macro->data;
271 break;
273 case VIS_OP_DELETE:
275 enum VisMode mode = vis_mode_get(vis);
276 enum VisRegister reg = vis_register_used(vis);
277 if (reg == VIS_REG_DEFAULT && (mode == VIS_MODE_INSERT || mode == VIS_MODE_REPLACE))
278 vis_register(vis, VIS_REG_BLACKHOLE);
279 break;
281 default:
282 break;
285 const Operator *op = NULL;
286 if (id < LENGTH(vis_operators))
287 op = &vis_operators[id];
288 else
289 op = array_get_ptr(&vis->operators, id - VIS_OP_LAST);
291 if (!op)
292 goto err;
294 if (vis->mode->visual) {
295 vis->action.op = op;
296 vis_do(vis);
297 goto out;
300 /* switch to operator mode inorder to make operator options and
301 * text-object available */
302 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
303 if (vis->action.op == op) {
304 /* hacky way to handle double operators i.e. things like
305 * dd, yy etc where the second char isn't a movement */
306 vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
307 vis_motion(vis, VIS_MOVE_LINE_NEXT);
308 } else {
309 vis->action.op = op;
312 /* put is not a real operator, does not need a range to operate on */
313 if (id == VIS_OP_PUT_AFTER)
314 vis_motion(vis, VIS_MOVE_NOP);
316 out:
317 va_end(ap);
318 return true;
319 err:
320 va_end(ap);
321 return false;
324 const Operator vis_operators[] = {
325 [VIS_OP_DELETE] = { op_delete },
326 [VIS_OP_CHANGE] = { op_change },
327 [VIS_OP_YANK] = { op_yank },
328 [VIS_OP_PUT_AFTER] = { op_put },
329 [VIS_OP_SHIFT_RIGHT] = { op_shift_right },
330 [VIS_OP_SHIFT_LEFT] = { op_shift_left },
331 [VIS_OP_JOIN] = { op_join },
332 [VIS_OP_MODESWITCH] = { op_modeswitch },
333 [VIS_OP_REPLACE] = { op_replace },
334 [VIS_OP_CURSOR_SOL] = { op_cursor },