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 };