man: add man page for vis-complete(1)
[vis.git] / vis-operators.c
blob8d28dc40b017f7b1ba5d8b4fdf2b45cd62ffd75f
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_put_range(vis, c->reg, 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 return c->range.start;
24 static size_t op_yank(Vis *vis, Text *txt, OperatorContext *c) {
25 c->reg->linewise = c->linewise;
26 register_put_range(vis, c->reg, txt, &c->range);
27 if (c->reg == &vis->registers[VIS_REG_DEFAULT]) {
28 vis->registers[VIS_REG_ZERO].linewise = c->reg->linewise;
29 register_put_range(vis, &vis->registers[VIS_REG_ZERO], txt, &c->range);
31 return c->linewise ? c->pos : c->range.start;
34 static size_t op_put(Vis *vis, Text *txt, OperatorContext *c) {
35 char b;
36 size_t pos = c->pos;
37 bool sel = text_range_size(&c->range) > 0;
38 bool sel_linewise = sel && text_range_is_linewise(txt, &c->range);
39 if (sel) {
40 text_delete_range(txt, &c->range);
41 pos = c->pos = c->range.start;
43 switch (c->arg->i) {
44 case VIS_OP_PUT_AFTER:
45 case VIS_OP_PUT_AFTER_END:
46 if (c->reg->linewise && !sel_linewise)
47 pos = text_line_next(txt, pos);
48 else if (!sel && text_byte_get(txt, pos, &b) && b != '\r' && b != '\n')
49 pos = text_char_next(txt, pos);
50 break;
51 case VIS_OP_PUT_BEFORE:
52 case VIS_OP_PUT_BEFORE_END:
53 if (c->reg->linewise)
54 pos = text_line_begin(txt, pos);
55 break;
58 size_t len;
59 const char *data = register_get(vis, c->reg, &len);
61 for (int i = 0; i < c->count; i++) {
62 char nl;
63 if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
64 pos += text_insert_newline(txt, pos);
65 text_insert(txt, pos, data, len);
66 pos += len;
67 if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
68 pos += text_insert_newline(txt, pos);
71 if (c->reg->linewise) {
72 switch (c->arg->i) {
73 case VIS_OP_PUT_BEFORE_END:
74 case VIS_OP_PUT_AFTER_END:
75 pos = text_line_start(txt, pos);
76 break;
77 case VIS_OP_PUT_AFTER:
78 pos = text_line_start(txt, text_line_next(txt, c->pos));
79 break;
80 case VIS_OP_PUT_BEFORE:
81 pos = text_line_start(txt, c->pos);
82 break;
84 } else {
85 switch (c->arg->i) {
86 case VIS_OP_PUT_AFTER:
87 case VIS_OP_PUT_BEFORE:
88 pos = text_char_prev(txt, pos);
89 break;
93 return pos;
96 static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) {
97 char spaces[9] = " ";
98 spaces[MIN(vis->tabwidth, LENGTH(spaces) - 1)] = '\0';
99 const char *tab = vis->expandtab ? spaces : "\t";
100 size_t tablen = strlen(tab);
101 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
102 size_t inserted = 0;
104 /* if range ends at the begin of a line, skip line break */
105 if (pos == c->range.end)
106 pos = text_line_prev(txt, pos);
108 do {
109 prev_pos = pos = text_line_begin(txt, pos);
110 text_insert(txt, pos, tab, tablen);
111 pos = text_line_prev(txt, pos);
112 inserted += tablen;
113 } while (pos >= c->range.start && pos != prev_pos);
115 return c->pos + inserted;
118 static size_t op_shift_left(Vis *vis, Text *txt, OperatorContext *c) {
119 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
120 size_t tabwidth = vis->tabwidth, tablen;
121 size_t deleted = 0;
123 /* if range ends at the begin of a line, skip line break */
124 if (pos == c->range.end)
125 pos = text_line_prev(txt, pos);
127 do {
128 char c;
129 size_t len = 0;
130 prev_pos = pos = text_line_begin(txt, pos);
131 Iterator it = text_iterator_get(txt, pos);
132 if (text_iterator_byte_get(&it, &c) && c == '\t') {
133 len = 1;
134 } else {
135 for (len = 0; text_iterator_byte_get(&it, &c) && c == ' '; len++)
136 text_iterator_byte_next(&it, NULL);
138 tablen = MIN(len, tabwidth);
139 text_delete(txt, pos, tablen);
140 pos = text_line_prev(txt, pos);
141 deleted += tablen;
142 } while (pos >= c->range.start && pos != prev_pos);
144 return c->pos - deleted;
147 static size_t op_case_change(Vis *vis, Text *txt, OperatorContext *c) {
148 size_t len = text_range_size(&c->range);
149 char *buf = malloc(len);
150 if (!buf)
151 return c->pos;
152 len = text_bytes_get(txt, c->range.start, len, buf);
153 size_t rem = len;
154 for (char *cur = buf; rem > 0; cur++, rem--) {
155 int ch = (unsigned char)*cur;
156 if (isascii(ch)) {
157 if (c->arg->i == VIS_OP_CASE_SWAP)
158 *cur = islower(ch) ? toupper(ch) : tolower(ch);
159 else if (c->arg->i == VIS_OP_CASE_UPPER)
160 *cur = toupper(ch);
161 else
162 *cur = tolower(ch);
166 text_delete(txt, c->range.start, len);
167 text_insert(txt, c->range.start, buf, len);
168 free(buf);
169 return c->pos;
172 static size_t op_cursor(Vis *vis, Text *txt, OperatorContext *c) {
173 View *view = vis->win->view;
174 Filerange r = text_range_linewise(txt, &c->range);
175 for (size_t line = text_range_line_first(txt, &r); line != EPOS; line = text_range_line_next(txt, &r, line)) {
176 size_t pos;
177 if (c->arg->i == VIS_OP_CURSOR_EOL)
178 pos = text_line_finish(txt, line);
179 else
180 pos = text_line_start(txt, line);
181 view_cursors_new_force(view, pos);
183 return EPOS;
186 static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) {
187 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
188 Mark mark = EMARK;
190 /* if operator and range are both linewise, skip last line break */
191 if (c->linewise && text_range_is_linewise(txt, &c->range)) {
192 size_t line_prev = text_line_prev(txt, pos);
193 size_t line_prev_prev = text_line_prev(txt, line_prev);
194 if (line_prev_prev >= c->range.start)
195 pos = line_prev;
198 size_t len = c->arg->s ? strlen(c->arg->s) : 0;
200 do {
201 prev_pos = pos;
202 size_t end = text_line_start(txt, pos);
203 pos = text_line_prev(txt, end);
204 if (pos < c->range.start || end <= pos)
205 break;
206 text_delete(txt, pos, end - pos);
207 char prev, next;
208 if (text_byte_get(txt, pos-1, &prev) && !isspace((unsigned char)prev) &&
209 text_byte_get(txt, pos, &next) && next != '\r' && next != '\n')
210 text_insert(txt, pos, c->arg->s, len);
211 if (mark == EMARK)
212 mark = text_mark_set(txt, pos);
213 } while (pos != prev_pos);
215 size_t newpos = text_mark_get(txt, mark);
216 return newpos != EPOS ? newpos : c->range.start;
219 static size_t op_modeswitch(Vis *vis, Text *txt, OperatorContext *c) {
220 return c->newpos != EPOS ? c->newpos : c->pos;
223 static size_t op_replace(Vis *vis, Text *txt, OperatorContext *c) {
224 size_t count = 0;
225 Iterator it = text_iterator_get(txt, c->range.start);
226 while (it. pos < c->range.end && text_iterator_char_next(&it, NULL))
227 count++;
228 op_delete(vis, txt, c);
229 size_t pos = c->range.start;
230 for (size_t len = strlen(c->arg->s); count > 0; pos += len, count--)
231 text_insert(txt, pos, c->arg->s, len);
232 return pos;
235 static size_t op_filter(Vis *vis, Text *txt, OperatorContext *c) {
236 return text_size(txt) + 1; /* do not change cursor position, would destroy selection */
239 bool vis_operator(Vis *vis, enum VisOperator id, ...) {
240 va_list ap;
241 va_start(ap, id);
243 switch (id) {
244 case VIS_OP_MODESWITCH:
245 vis->action.mode = va_arg(ap, int);
246 break;
247 case VIS_OP_CASE_LOWER:
248 case VIS_OP_CASE_UPPER:
249 case VIS_OP_CASE_SWAP:
250 vis->action.arg.i = id;
251 id = VIS_OP_CASE_SWAP;
252 break;
253 case VIS_OP_CURSOR_SOL:
254 case VIS_OP_CURSOR_EOL:
255 vis->action.arg.i = id;
256 id = VIS_OP_CURSOR_SOL;
257 break;
258 case VIS_OP_PUT_AFTER:
259 case VIS_OP_PUT_AFTER_END:
260 case VIS_OP_PUT_BEFORE:
261 case VIS_OP_PUT_BEFORE_END:
262 vis->action.arg.i = id;
263 id = VIS_OP_PUT_AFTER;
264 break;
265 case VIS_OP_JOIN:
266 vis->action.arg.s = va_arg(ap, char*);
267 break;
268 case VIS_OP_FILTER:
269 vis->action.arg.s = va_arg(ap, char*);
270 /* fall through */
271 case VIS_OP_SHIFT_LEFT:
272 case VIS_OP_SHIFT_RIGHT:
273 vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
274 break;
275 case VIS_OP_REPLACE:
277 Macro *macro = &vis->registers[VIS_MACRO_OPERATOR].buf;
278 macro_reset(macro);
279 macro_append(macro, va_arg(ap, char*));
280 vis->action.arg.s = macro->data;
281 break;
283 default:
284 break;
286 if (id >= LENGTH(vis_operators))
287 goto err;
288 const Operator *op = &vis_operators[id];
289 if (vis->mode->visual) {
290 vis->action.op = op;
291 vis_do(vis);
292 goto out;
295 /* switch to operator mode inorder to make operator options and
296 * text-object available */
297 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
298 if (vis->action.op == op) {
299 /* hacky way to handle double operators i.e. things like
300 * dd, yy etc where the second char isn't a movement */
301 vis->action.type = LINEWISE;
302 vis_motion(vis, VIS_MOVE_LINE_NEXT);
303 } else {
304 vis->action.op = op;
307 /* put is not a real operator, does not need a range to operate on */
308 if (id == VIS_OP_PUT_AFTER)
309 vis_motion(vis, VIS_MOVE_NOP);
311 out:
312 va_end(ap);
313 return true;
314 err:
315 va_end(ap);
316 return false;
319 const Operator vis_operators[] = {
320 [VIS_OP_DELETE] = { op_delete },
321 [VIS_OP_CHANGE] = { op_change },
322 [VIS_OP_YANK] = { op_yank },
323 [VIS_OP_PUT_AFTER] = { op_put },
324 [VIS_OP_SHIFT_RIGHT] = { op_shift_right },
325 [VIS_OP_SHIFT_LEFT] = { op_shift_left },
326 [VIS_OP_CASE_SWAP] = { op_case_change },
327 [VIS_OP_JOIN] = { op_join },
328 [VIS_OP_MODESWITCH] = { op_modeswitch },
329 [VIS_OP_REPLACE] = { op_replace },
330 [VIS_OP_CURSOR_SOL] = { op_cursor },
331 [VIS_OP_FILTER] = { op_filter },