lexer: add fstab
[vis.git] / vis-operators.c
blobefc0dff9a4548e781e7126353370a55e246350a4
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 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_range(vis, c->reg, txt, &c->range);
28 if (c->reg == &vis->registers[VIS_REG_DEFAULT])
29 register_put_range(vis, &vis->registers[VIS_REG_ZERO], txt, &c->range);
30 return c->linewise ? c->pos : c->range.start;
33 static size_t op_put(Vis *vis, Text *txt, OperatorContext *c) {
34 char b;
35 size_t pos = c->pos;
36 bool sel = text_range_size(&c->range) > 0;
37 bool sel_linewise = sel && text_range_is_linewise(txt, &c->range);
38 if (sel) {
39 text_delete_range(txt, &c->range);
40 pos = c->pos = c->range.start;
42 switch (c->arg->i) {
43 case VIS_OP_PUT_AFTER:
44 case VIS_OP_PUT_AFTER_END:
45 if (c->reg->linewise && !sel_linewise)
46 pos = text_line_next(txt, pos);
47 else if (!sel && text_byte_get(txt, pos, &b) && b != '\r' && b != '\n')
48 pos = text_char_next(txt, pos);
49 break;
50 case VIS_OP_PUT_BEFORE:
51 case VIS_OP_PUT_BEFORE_END:
52 if (c->reg->linewise)
53 pos = text_line_begin(txt, pos);
54 break;
57 size_t len;
58 const char *data = register_get(vis, c->reg, &len);
60 for (int i = 0; i < c->count; i++) {
61 char nl;
62 if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
63 pos += text_insert_newline(txt, pos);
64 text_insert(txt, pos, data, len);
65 pos += len;
66 if (c->reg->linewise && pos > 0 && text_byte_get(txt, pos-1, &nl) && nl != '\n')
67 pos += text_insert_newline(txt, pos);
70 if (c->reg->linewise) {
71 switch (c->arg->i) {
72 case VIS_OP_PUT_BEFORE_END:
73 case VIS_OP_PUT_AFTER_END:
74 pos = text_line_start(txt, pos);
75 break;
76 case VIS_OP_PUT_AFTER:
77 pos = text_line_start(txt, text_line_next(txt, c->pos));
78 break;
79 case VIS_OP_PUT_BEFORE:
80 pos = text_line_start(txt, c->pos);
81 break;
83 } else {
84 switch (c->arg->i) {
85 case VIS_OP_PUT_AFTER:
86 case VIS_OP_PUT_BEFORE:
87 pos = text_char_prev(txt, pos);
88 break;
92 return pos;
95 static size_t op_shift_right(Vis *vis, Text *txt, OperatorContext *c) {
96 char spaces[9] = " ";
97 spaces[MIN(vis->tabwidth, LENGTH(spaces) - 1)] = '\0';
98 const char *tab = vis->expandtab ? spaces : "\t";
99 size_t tablen = strlen(tab);
100 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
101 size_t inserted = 0;
103 /* if range ends at the begin of a line, skip line break */
104 if (pos == c->range.end)
105 pos = text_line_prev(txt, pos);
107 do {
108 prev_pos = pos = text_line_begin(txt, pos);
109 text_insert(txt, pos, tab, tablen);
110 pos = text_line_prev(txt, pos);
111 inserted += tablen;
112 } while (pos >= c->range.start && pos != prev_pos);
114 return c->pos + inserted;
117 static size_t op_shift_left(Vis *vis, Text *txt, OperatorContext *c) {
118 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
119 size_t tabwidth = vis->tabwidth, tablen;
120 size_t deleted = 0;
122 /* if range ends at the begin of a line, skip line break */
123 if (pos == c->range.end)
124 pos = text_line_prev(txt, pos);
126 do {
127 char c;
128 size_t len = 0;
129 prev_pos = pos = text_line_begin(txt, pos);
130 Iterator it = text_iterator_get(txt, pos);
131 if (text_iterator_byte_get(&it, &c) && c == '\t') {
132 len = 1;
133 } else {
134 for (len = 0; text_iterator_byte_get(&it, &c) && c == ' '; len++)
135 text_iterator_byte_next(&it, NULL);
137 tablen = MIN(len, tabwidth);
138 text_delete(txt, pos, tablen);
139 pos = text_line_prev(txt, pos);
140 deleted += tablen;
141 } while (pos >= c->range.start && pos != prev_pos);
143 return c->pos - deleted;
146 static size_t op_case_change(Vis *vis, Text *txt, OperatorContext *c) {
147 size_t len = text_range_size(&c->range);
148 char *buf = malloc(len);
149 if (!buf)
150 return c->pos;
151 len = text_bytes_get(txt, c->range.start, len, buf);
152 size_t rem = len;
153 for (char *cur = buf; rem > 0; cur++, rem--) {
154 int ch = (unsigned char)*cur;
155 if (isascii(ch)) {
156 if (c->arg->i == VIS_OP_CASE_SWAP)
157 *cur = islower(ch) ? toupper(ch) : tolower(ch);
158 else if (c->arg->i == VIS_OP_CASE_UPPER)
159 *cur = toupper(ch);
160 else
161 *cur = tolower(ch);
165 text_delete(txt, c->range.start, len);
166 text_insert(txt, c->range.start, buf, len);
167 free(buf);
168 return c->pos;
171 static size_t op_cursor(Vis *vis, Text *txt, OperatorContext *c) {
172 View *view = vis->win->view;
173 Filerange r = text_range_linewise(txt, &c->range);
174 for (size_t line = text_range_line_first(txt, &r); line != EPOS; line = text_range_line_next(txt, &r, line)) {
175 size_t pos;
176 if (c->arg->i == VIS_OP_CURSOR_EOL)
177 pos = text_line_finish(txt, line);
178 else
179 pos = text_line_start(txt, line);
180 view_cursors_new_force(view, pos);
182 return EPOS;
185 static size_t op_join(Vis *vis, Text *txt, OperatorContext *c) {
186 size_t pos = text_line_begin(txt, c->range.end), prev_pos;
187 Mark mark = NULL;
189 /* if operator and range are both linewise, skip last line break */
190 if (c->linewise && text_range_is_linewise(txt, &c->range)) {
191 size_t line_prev = text_line_prev(txt, pos);
192 size_t line_prev_prev = text_line_prev(txt, line_prev);
193 if (line_prev_prev >= c->range.start)
194 pos = line_prev;
197 do {
198 prev_pos = pos;
199 size_t end = text_line_start(txt, pos);
200 pos = text_char_next(txt, text_line_finish(txt, text_line_prev(txt, end)));
201 if (pos >= c->range.start && end > pos) {
202 text_delete(txt, pos, end - pos);
203 text_insert(txt, pos, " ", 1);
204 if (!mark)
205 mark = text_mark_set(txt, pos);
206 } else {
207 break;
209 } while (pos != prev_pos);
211 size_t newpos = text_mark_get(txt, mark);
212 return newpos != EPOS ? newpos : c->range.start;
215 static size_t op_insert(Vis *vis, Text *txt, OperatorContext *c) {
216 macro_operator_record(vis);
217 return c->newpos != EPOS ? c->newpos : c->pos;
220 static size_t op_replace(Vis *vis, Text *txt, OperatorContext *c) {
221 macro_operator_record(vis);
222 return c->newpos != EPOS ? c->newpos : c->pos;
225 static size_t op_filter(Vis *vis, Text *txt, OperatorContext *c) {
226 if (!c->arg->s)
227 macro_operator_record(vis);
228 return text_size(txt) + 1; /* do not change cursor position, would destroy selection */
231 bool vis_operator(Vis *vis, enum VisOperator id, ...) {
232 va_list ap;
233 va_start(ap, id);
235 switch (id) {
236 case VIS_OP_CASE_LOWER:
237 case VIS_OP_CASE_UPPER:
238 case VIS_OP_CASE_SWAP:
239 vis->action.arg.i = id;
240 id = VIS_OP_CASE_SWAP;
241 break;
242 case VIS_OP_CURSOR_SOL:
243 case VIS_OP_CURSOR_EOL:
244 vis->action.arg.i = id;
245 id = VIS_OP_CURSOR_SOL;
246 break;
247 case VIS_OP_PUT_AFTER:
248 case VIS_OP_PUT_AFTER_END:
249 case VIS_OP_PUT_BEFORE:
250 case VIS_OP_PUT_BEFORE_END:
251 vis->action.arg.i = id;
252 id = VIS_OP_PUT_AFTER;
253 break;
254 case VIS_OP_FILTER:
255 vis->action.arg.s = va_arg(ap, char*);
256 /* fall through */
257 case VIS_OP_SHIFT_LEFT:
258 case VIS_OP_SHIFT_RIGHT:
259 vis_motion_type(vis, VIS_MOTIONTYPE_LINEWISE);
260 break;
261 default:
262 break;
264 if (id >= LENGTH(vis_operators))
265 goto err;
266 const Operator *op = &vis_operators[id];
267 if (vis->mode->visual) {
268 vis->action.op = op;
269 action_do(vis, &vis->action);
270 goto out;
273 /* switch to operator mode inorder to make operator options and
274 * text-object available */
275 vis_mode_switch(vis, VIS_MODE_OPERATOR_PENDING);
276 if (vis->action.op == op) {
277 /* hacky way to handle double operators i.e. things like
278 * dd, yy etc where the second char isn't a movement */
279 vis->action.type = LINEWISE;
280 vis_motion(vis, VIS_MOVE_LINE_NEXT);
281 } else {
282 vis->action.op = op;
285 /* put is not a real operator, does not need a range to operate on */
286 if (id == VIS_OP_PUT_AFTER)
287 vis_motion(vis, VIS_MOVE_NOP);
289 out:
290 va_end(ap);
291 return true;
292 err:
293 va_end(ap);
294 return false;
297 const Operator vis_operators[] = {
298 [VIS_OP_DELETE] = { op_delete },
299 [VIS_OP_CHANGE] = { op_change },
300 [VIS_OP_YANK] = { op_yank },
301 [VIS_OP_PUT_AFTER] = { op_put },
302 [VIS_OP_SHIFT_RIGHT] = { op_shift_right },
303 [VIS_OP_SHIFT_LEFT] = { op_shift_left },
304 [VIS_OP_CASE_SWAP] = { op_case_change },
305 [VIS_OP_JOIN] = { op_join },
306 [VIS_OP_INSERT] = { op_insert },
307 [VIS_OP_REPLACE] = { op_replace },
308 [VIS_OP_CURSOR_SOL] = { op_cursor },
309 [VIS_OP_FILTER] = { op_filter },