vis: implement gn and gN text objects
[vis.git] / vis-operators.c
blobd4fdb2132a31bb5330858cf4b617989922566994
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(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 macro_operator_record(vis);
22 return c->range.start;
25 static size_t op_yank(Vis *vis, Text *txt, OperatorContext *c) {
26 c->reg->linewise = c->linewise;
27 register_put(vis, c->reg, txt, &c->range);
28 if (c->reg == &vis->registers[VIS_REG_DEFAULT])
29 register_put(vis, &vis->registers[VIS_REG_ZERO], txt, &c->range);
30 return c->pos;
33 static size_t op_put(Vis *vis, Text *txt, OperatorContext *c) {
34 size_t pos = c->pos;
35 bool sel = text_range_size(&c->range) > 0;
36 bool sel_linewise = sel && text_range_is_linewise(txt, &c->range);
37 if (sel) {
38 text_delete_range(txt, &c->range);
39 pos = c->pos = c->range.start;
41 switch (c->arg->i) {
42 case VIS_OP_PUT_AFTER:
43 case VIS_OP_PUT_AFTER_END:
44 if (c->reg->linewise && !sel_linewise)
45 pos = text_line_next(txt, pos);
46 else if (!sel)
47 pos = text_char_next(txt, pos);
48 break;
49 case VIS_OP_PUT_BEFORE:
50 case VIS_OP_PUT_BEFORE_END:
51 if (c->reg->linewise)
52 pos = text_line_begin(txt, pos);
53 break;
56 size_t len;
57 const char *data = register_get(vis, c->reg, &len);
59 for (int i = 0; i < c->count; i++) {
60 text_insert(txt, pos, data, len);
61 pos += len;
64 if (c->reg->linewise) {
65 switch (c->arg->i) {
66 case VIS_OP_PUT_BEFORE_END:
67 case VIS_OP_PUT_AFTER_END:
68 pos = text_line_start(txt, pos);
69 break;
70 case VIS_OP_PUT_AFTER:
71 pos = text_line_start(txt, text_line_next(txt, c->pos));
72 break;
73 case VIS_OP_PUT_BEFORE:
74 pos = text_line_start(txt, c->pos);
75 break;
77 } else {
78 switch (c->arg->i) {
79 case VIS_OP_PUT_AFTER:
80 case VIS_OP_PUT_BEFORE:
81 pos = text_char_prev(txt, pos);
82 break;
86 return pos;
89 static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) {
90 char spaces[9] = " ";
91 spaces[MIN(vis->tabwidth, LENGTH(spaces) - 1)] = '\0';
92 const char *tab = vis->expandtab ? spaces : "\t";
93 size_t tablen = strlen(tab);
94 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
95 size_t inserted = 0;
97 /* if range ends at the begin of a line, skip line break */
98 if (pos == c->range.end)
99 pos = text_line_prev(txt, pos);
101 do {
102 prev_pos = pos = text_line_begin(txt, pos);
103 text_insert(txt, pos, tab, tablen);
104 pos = text_line_prev(txt, pos);
105 inserted += tablen;
106 } while (pos >= c->range.start && pos != prev_pos);
108 return c->pos + inserted;
111 static size_t op_shift_left(Vis *vis, Text *txt, OperatorContext *c) {
112 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
113 size_t tabwidth = vis->tabwidth, tablen;
114 size_t deleted = 0;
116 /* if range ends at the begin of a line, skip line break */
117 if (pos == c->range.end)
118 pos = text_line_prev(txt, pos);
120 do {
121 char c;
122 size_t len = 0;
123 prev_pos = pos = text_line_begin(txt, pos);
124 Iterator it = text_iterator_get(txt, pos);
125 if (text_iterator_byte_get(&it, &c) && c == '\t') {
126 len = 1;
127 } else {
128 for (len = 0; text_iterator_byte_get(&it, &c) && c == ' '; len++)
129 text_iterator_byte_next(&it, NULL);
131 tablen = MIN(len, tabwidth);
132 text_delete(txt, pos, tablen);
133 pos = text_line_prev(txt, pos);
134 deleted += tablen;
135 } while (pos >= c->range.start && pos != prev_pos);
137 return c->pos - deleted;
140 static size_t op_case_change(Vis *vis, Text *txt, OperatorContext *c) {
141 size_t len = text_range_size(&c->range);
142 char *buf = malloc(len);
143 if (!buf)
144 return c->pos;
145 len = text_bytes_get(txt, c->range.start, len, buf);
146 size_t rem = len;
147 for (char *cur = buf; rem > 0; cur++, rem--) {
148 int ch = (unsigned char)*cur;
149 if (isascii(ch)) {
150 if (c->arg->i == VIS_OP_CASE_SWAP)
151 *cur = islower(ch) ? toupper(ch) : tolower(ch);
152 else if (c->arg->i == VIS_OP_CASE_UPPER)
153 *cur = toupper(ch);
154 else
155 *cur = tolower(ch);
159 text_delete(txt, c->range.start, len);
160 text_insert(txt, c->range.start, buf, len);
161 free(buf);
162 return c->pos;
165 static size_t op_cursor(Vis *vis, Text *txt, OperatorContext *c) {
166 View *view = vis->win->view;
167 Filerange r = text_range_linewise(txt, &c->range);
168 for (size_t line = text_range_line_first(txt, &r); line != EPOS; line = text_range_line_next(txt, &r, line)) {
169 Cursor *cursor = view_cursors_new(view);
170 if (cursor) {
171 size_t pos;
172 if (c->arg->i == VIS_OP_CURSOR_EOL)
173 pos = text_line_finish(txt, line);
174 else
175 pos = text_line_start(txt, line);
176 view_cursors_to(cursor, pos);
179 return EPOS;
182 static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) {
183 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
185 /* if operator and range are both linewise, skip last line break */
186 if (c->linewise && text_range_is_linewise(txt, &c->range)) {
187 size_t line_prev = text_line_prev(txt, pos);
188 size_t line_prev_prev = text_line_prev(txt, line_prev);
189 if (line_prev_prev >= c->range.start)
190 pos = line_prev;
193 do {
194 prev_pos = pos;
195 size_t end = text_line_start(txt, pos);
196 pos = text_char_next(txt, text_line_finish(txt, text_line_prev(txt, end)));
197 if (pos >= c->range.start && end > pos) {
198 text_delete(txt, pos, end - pos);
199 text_insert(txt, pos, " ", 1);
200 } else {
201 break;
203 } while (pos != prev_pos);
205 return c->range.start;
208 static size_t op_insert(Vis *vis, Text *txt, OperatorContext *c) {
209 macro_operator_record(vis);
210 return c->newpos != EPOS ? c->newpos : c->pos;
213 static size_t op_replace(Vis *vis, Text *txt, OperatorContext *c) {
214 macro_operator_record(vis);
215 return c->newpos != EPOS ? c->newpos : c->pos;
218 static size_t op_filter(Vis *vis, Text *txt, OperatorContext *c) {
219 if (!c->arg->s)
220 macro_operator_record(vis);
221 return text_size(txt) + 1; /* do not change cursor position, would destroy selection */
224 bool vis_operator(Vis *vis, enum VisOperator id, ...) {
225 va_list ap;
226 va_start(ap, id);
228 switch (id) {
229 case VIS_OP_CASE_LOWER:
230 case VIS_OP_CASE_UPPER:
231 case VIS_OP_CASE_SWAP:
232 vis->action.arg.i = id;
233 id = VIS_OP_CASE_SWAP;
234 break;
235 case VIS_OP_CURSOR_SOL:
236 case VIS_OP_CURSOR_EOL:
237 vis->action.arg.i = id;
238 id = VIS_OP_CURSOR_SOL;
239 break;
240 case VIS_OP_PUT_AFTER:
241 case VIS_OP_PUT_AFTER_END:
242 case VIS_OP_PUT_BEFORE:
243 case VIS_OP_PUT_BEFORE_END:
244 vis->action.arg.i = id;
245 id = VIS_OP_PUT_AFTER;
246 break;
247 case VIS_OP_FILTER:
248 vis->action.arg.s = va_arg(ap, char*);
249 /* fall through */
250 case VIS_OP_SHIFT_LEFT:
251 case VIS_OP_SHIFT_RIGHT:
252 vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
253 break;
254 default:
255 break;
257 if (id >= LENGTH(vis_operators))
258 goto err;
259 Operator *op = &vis_operators[id];
260 if (vis->mode->visual) {
261 vis->action.op = op;
262 action_do(vis, &vis->action);
263 goto out;
266 /* switch to operator mode inorder to make operator options and
267 * text-object available */
268 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
269 if (vis->action.op == op) {
270 /* hacky way to handle double operators i.e. things like
271 * dd, yy etc where the second char isn't a movement */
272 vis->action.type = LINEWISE;
273 vis_motion(vis, VIS_MOVE_LINE_NEXT);
274 } else {
275 vis->action.op = op;
278 /* put is not a real operator, does not need a range to operate on */
279 if (id == VIS_OP_PUT_AFTER)
280 vis_motion(vis, VIS_MOVE_NOP);
282 out:
283 va_end(ap);
284 return true;
285 err:
286 va_end(ap);
287 return false;
290 Operator vis_operators[] = {
291 [VIS_OP_DELETE] = { op_delete },
292 [VIS_OP_CHANGE] = { op_change },
293 [VIS_OP_YANK] = { op_yank },
294 [VIS_OP_PUT_AFTER] = { op_put },
295 [VIS_OP_SHIFT_RIGHT] = { op_shift_right },
296 [VIS_OP_SHIFT_LEFT] = { op_shift_left },
297 [VIS_OP_CASE_SWAP] = { op_case_change },
298 [VIS_OP_JOIN] = { op_join },
299 [VIS_OP_INSERT] = { op_insert },
300 [VIS_OP_REPLACE] = { op_replace },
301 [VIS_OP_CURSOR_SOL] = { op_cursor },
302 [VIS_OP_FILTER] = { op_filter },