1 #include <string.h>
2 #include <ctype.h>
3 #include "mle.h"
4 
5 // Clone cursor
cursor_clone(cursor_t * cursor,int use_srules,cursor_t ** ret_clone)6 int cursor_clone(cursor_t *cursor, int use_srules, cursor_t **ret_clone) {
7     cursor_t *clone;
8     bview_add_cursor(cursor->bview, cursor->mark->bline, cursor->mark->col, &clone);
9     if (cursor->is_anchored) {
10         cursor_toggle_anchor(clone, use_srules);
11         mark_join(clone->anchor, cursor->anchor);
12     }
13     *ret_clone = clone;
14     return MLE_OK;
15 }
16 
17 // Remove cursor
cursor_destroy(cursor_t * cursor)18 int cursor_destroy(cursor_t *cursor) {
19     return bview_remove_cursor(cursor->bview, cursor);
20 }
21 
22 // Select by mark
cursor_select_between(cursor_t * cursor,mark_t * a,mark_t * b,int use_srules)23 int cursor_select_between(cursor_t *cursor, mark_t *a, mark_t *b, int use_srules) {
24     cursor_drop_anchor(cursor, use_srules);
25     if (mark_is_lt(a, b)) {
26         mark_join(cursor->mark, a);
27         mark_join(cursor->anchor, b);
28     } else {
29         mark_join(cursor->mark, b);
30         mark_join(cursor->anchor, a);
31     }
32     return MLE_OK;
33 }
34 
35 // Toggle cursor anchor
cursor_toggle_anchor(cursor_t * cursor,int use_srules)36 int cursor_toggle_anchor(cursor_t *cursor, int use_srules) {
37     if (!cursor->is_anchored) {
38         mark_clone(cursor->mark, &(cursor->anchor));
39         if (use_srules) {
40             cursor->sel_rule = srule_new_range(cursor->mark, cursor->anchor, 0, TB_REVERSE);
41             buffer_add_srule(cursor->bview->buffer, cursor->sel_rule);
42         }
43         cursor->is_anchored = 1;
44     } else {
45         if (use_srules && cursor->sel_rule) {
46             buffer_remove_srule(cursor->bview->buffer, cursor->sel_rule);
47             srule_destroy(cursor->sel_rule);
48             cursor->sel_rule = NULL;
49         }
50         mark_destroy(cursor->anchor);
51         cursor->is_anchored = 0;
52     }
53     return MLE_OK;
54 }
55 
56 // Drop cursor anchor
cursor_drop_anchor(cursor_t * cursor,int use_srules)57 int cursor_drop_anchor(cursor_t *cursor, int use_srules) {
58     if (cursor->is_anchored) return MLE_OK;
59     return cursor_toggle_anchor(cursor, use_srules);
60 }
61 
62 // Lift cursor anchor
cursor_lift_anchor(cursor_t * cursor)63 int cursor_lift_anchor(cursor_t *cursor) {
64     if (!cursor->is_anchored) return MLE_OK;
65     return cursor_toggle_anchor(cursor, cursor->sel_rule ? 1 : 0);
66 }
67 
68 // Get lo and hi marks in a is_anchored=1 cursor
cursor_get_lo_hi(cursor_t * cursor,mark_t ** ret_lo,mark_t ** ret_hi)69 int cursor_get_lo_hi(cursor_t *cursor, mark_t **ret_lo, mark_t **ret_hi) {
70     if (!cursor->is_anchored) {
71         return MLE_ERR;
72     }
73     if (mark_is_gt(cursor->anchor, cursor->mark)) {
74         *ret_lo = cursor->mark;
75         *ret_hi = cursor->anchor;
76     } else {
77         *ret_lo = cursor->anchor;
78         *ret_hi = cursor->mark;
79     }
80     return MLE_OK;
81 }
82 
83 // Get mark
cursor_get_mark(cursor_t * cursor,mark_t ** ret_mark)84 int cursor_get_mark(cursor_t *cursor, mark_t **ret_mark) {
85     *ret_mark = cursor->mark;
86     return MLE_OK;
87 }
88 
89 // Get anchor if anchored
cursor_get_anchor(cursor_t * cursor,mark_t ** ret_anchor)90 int cursor_get_anchor(cursor_t *cursor, mark_t **ret_anchor) {
91     *ret_anchor = cursor->is_anchored ? cursor->anchor : NULL;
92     return MLE_OK;
93 }
94 
95 // Make selection by strat
cursor_select_by(cursor_t * cursor,const char * strat,int use_srules)96 int cursor_select_by(cursor_t *cursor, const char *strat, int use_srules) {
97     if (cursor->is_anchored) {
98         return MLE_ERR;
99     }
100     if (strcmp(strat, "bracket") == 0) {
101         return cursor_select_by_bracket(cursor, use_srules);
102     } else if (strcmp(strat, "word") == 0) {
103         return cursor_select_by_word(cursor, use_srules);
104     } else if (strcmp(strat, "word_back") == 0) {
105         return cursor_select_by_word_back(cursor, use_srules);
106     } else if (strcmp(strat, "word_forward") == 0) {
107         return cursor_select_by_word_forward(cursor, use_srules);
108     } else if (strcmp(strat, "eol") == 0 && !mark_is_at_eol(cursor->mark)) {
109         cursor_toggle_anchor(cursor, use_srules);
110         mark_move_eol(cursor->anchor);
111     } else if (strcmp(strat, "bol") == 0 && !mark_is_at_bol(cursor->mark)) {
112         cursor_toggle_anchor(cursor, use_srules);
113         mark_move_bol(cursor->anchor);
114     } else if (strcmp(strat, "string") == 0) {
115         return cursor_select_by_string(cursor, use_srules);
116     } else {
117         MLE_RETURN_ERR(cursor->bview->editor, "Unrecognized cursor_select_by strat '%s'", strat);
118     }
119     return MLE_OK;
120 }
121 
122 // Select by bracket
cursor_select_by_bracket(cursor_t * cursor,int use_srules)123 int cursor_select_by_bracket(cursor_t *cursor, int use_srules) {
124     mark_t *orig;
125     mark_clone(cursor->mark, &orig);
126     if (mark_move_bracket_top(cursor->mark, MLE_BRACKET_PAIR_MAX_SEARCH) != MLBUF_OK) {
127         mark_destroy(orig);
128         return MLE_ERR;
129     }
130     cursor_toggle_anchor(cursor, use_srules);
131     if (mark_move_bracket_pair(cursor->anchor, MLE_BRACKET_PAIR_MAX_SEARCH) != MLBUF_OK) {
132         cursor_toggle_anchor(cursor, 0);
133         mark_join(cursor->mark, orig);
134         mark_destroy(orig);
135         return MLE_ERR;
136     }
137     mark_move_by(cursor->mark, 1);
138     mark_destroy(orig);
139     return MLE_OK;
140 }
141 
142 // Select by word-back
cursor_select_by_word_back(cursor_t * cursor,int use_srules)143 int cursor_select_by_word_back(cursor_t *cursor, int use_srules) {
144     if (mark_is_at_word_bound(cursor->mark, -1)) return MLE_ERR;
145     cursor_toggle_anchor(cursor, use_srules);
146     mark_move_prev_re(cursor->mark, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1);
147     return MLE_OK;
148 }
149 
150 // Select by word-forward
cursor_select_by_word_forward(cursor_t * cursor,int use_srules)151 int cursor_select_by_word_forward(cursor_t *cursor, int use_srules) {
152     if (mark_is_at_word_bound(cursor->mark, 1)) return MLE_ERR;
153     cursor_toggle_anchor(cursor, use_srules);
154     mark_move_next_re(cursor->mark, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1);
155     return MLE_OK;
156 }
157 
158 // Select by string
cursor_select_by_string(cursor_t * cursor,int use_srules)159 int cursor_select_by_string(cursor_t *cursor, int use_srules) {
160     mark_t *orig;
161     uint32_t qchar;
162     char *qre;
163     mark_clone(cursor->mark, &orig);
164     if (mark_move_prev_re(cursor->mark, "(?<!\\\\)[`'\"]", strlen("(?<!\\\\)[`'\"]")) != MLBUF_OK) {
165         mark_destroy(orig);
166         return MLE_ERR;
167     }
168     cursor_toggle_anchor(cursor, use_srules);
169     mark_get_char_after(cursor->mark, &qchar);
170     mark_move_by(cursor->mark, 1);
171     if (qchar == '"') {
172         qre = "(?<!\\\\)\"";
173     } else if (qchar == '\'') {
174         qre = "(?<!\\\\)'";
175     } else {
176         qre = "(?<!\\\\)`";
177     }
178     if (mark_move_next_re_nudge(cursor->anchor, qre, strlen(qre)) != MLBUF_OK) {
179         cursor_toggle_anchor(cursor, use_srules);
180         mark_join(cursor->mark, orig);
181         mark_destroy(orig);
182         return MLE_ERR;
183     }
184     mark_destroy(orig);
185     return MLE_OK;
186 }
187 
188 // Select by word
cursor_select_by_word(cursor_t * cursor,int use_srules)189 int cursor_select_by_word(cursor_t *cursor, int use_srules) {
190     uint32_t after;
191     if (mark_is_at_eol(cursor->mark)) return MLE_ERR;
192     mark_get_char_after(cursor->mark, &after);
193     if (!isalnum((char)after) && (char)after != '_') return MLE_ERR;
194     if (!mark_is_at_word_bound(cursor->mark, -1)) {
195         mark_move_prev_re(cursor->mark, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1);
196     }
197     cursor_toggle_anchor(cursor, use_srules);
198     mark_move_next_re(cursor->mark, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1);
199     return MLE_OK;
200 }
201 
202 // Cut or copy text
cursor_cut_copy(cursor_t * cursor,int is_cut,int use_srules,int append)203 int cursor_cut_copy(cursor_t *cursor, int is_cut, int use_srules, int append) {
204     char *cutbuf;
205     bint_t cutbuf_len;
206     bint_t cur_len;
207     if (!append && cursor->cut_buffer) {
208         free(cursor->cut_buffer);
209         cursor->cut_buffer = NULL;
210     }
211     if (!cursor->is_anchored) {
212         use_srules = 0;
213         cursor_toggle_anchor(cursor, use_srules);
214         mark_move_bol(cursor->mark);
215         mark_move_eol(cursor->anchor);
216         mark_move_by(cursor->anchor, 1);
217     }
218     mark_get_between_mark(cursor->mark, cursor->anchor, &cutbuf, &cutbuf_len);
219     if (append && cursor->cut_buffer) {
220         cur_len = strlen(cursor->cut_buffer);
221         cursor->cut_buffer = realloc(cursor->cut_buffer, cur_len + cutbuf_len + 1);
222         strncat(cursor->cut_buffer, cutbuf, cutbuf_len);
223         free(cutbuf);
224     } else {
225         cursor->cut_buffer = cutbuf;
226     }
227     if (cursor->bview->editor->cut_buffer) free(cursor->bview->editor->cut_buffer);
228     cursor->bview->editor->cut_buffer = strdup(cursor->cut_buffer);
229     if (is_cut) {
230         mark_delete_between_mark(cursor->mark, cursor->anchor);
231     }
232     cursor_toggle_anchor(cursor, use_srules);
233     return MLE_OK;
234 }
235 
236 // Uncut (paste) text
cursor_uncut(cursor_t * cursor)237 int cursor_uncut(cursor_t *cursor) {
238     char *cut_buffer;
239     cut_buffer = cursor->cut_buffer ? cursor->cut_buffer : cursor->bview->editor->cut_buffer;
240     if (!cut_buffer) return MLE_ERR;
241     mark_insert_before(cursor->mark, cut_buffer, strlen(cut_buffer));
242     return MLE_OK;
243 }
244 
245 // Regex search and replace
cursor_replace(cursor_t * cursor,int interactive,char * opt_regex,char * opt_replacement)246 int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt_replacement) {
247     char *regex;
248     char *replacement;
249     int wrapped;
250     int all;
251     char *yn;
252     mark_t *lo_mark;
253     mark_t *hi_mark;
254     mark_t *orig_mark;
255     mark_t *search_mark;
256     mark_t *search_mark_end;
257     int anchored_before;
258     srule_t *highlight;
259     bline_t *bline;
260     bint_t col;
261     bint_t char_count;
262     bint_t orig_viewport_y;
263     int pcre_rc;
264     int pcre_ovector[30];
265     str_t repl_backref = {0};
266     int num_replacements;
267 
268     if (!interactive && (!opt_regex || !opt_replacement)) {
269         return MLE_ERR;
270     }
271 
272     regex = NULL;
273     replacement = NULL;
274     wrapped = 0;
275     lo_mark = NULL;
276     hi_mark = NULL;
277     orig_mark = NULL;
278     search_mark = NULL;
279     search_mark_end = NULL;
280     anchored_before = 0;
281     all = interactive ? 0 : 1;
282     num_replacements = 0;
283     mark_set_pcre_capture(&pcre_rc, pcre_ovector, 30);
284     orig_viewport_y = -1;
285 
286     do {
287         if (!interactive) {
288             regex = strdup(opt_regex);
289             replacement = strdup(opt_replacement);
290         } else {
291             editor_prompt(cursor->bview->editor, "replace: Search regex?", NULL, &regex);
292             if (!regex) break;
293             editor_prompt(cursor->bview->editor, "replace: Replacement string?", NULL, &replacement);
294             if (!replacement) break;
295         }
296         orig_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0);
297         lo_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0);
298         hi_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0);
299         search_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0);
300         search_mark_end = buffer_add_mark(cursor->bview->buffer, NULL, 0);
301         mark_join(search_mark, cursor->mark);
302         mark_join(orig_mark, cursor->mark);
303         orig_viewport_y = cursor->bview->viewport_y;
304         orig_mark->lefty = 1; // lefty==1 avoids moving when text is inserted at mark
305         lo_mark->lefty = 1;
306         if (cursor->is_anchored) {
307             anchored_before = mark_is_gt(cursor->mark, cursor->anchor);
308             mark_join(lo_mark, !anchored_before ? cursor->mark : cursor->anchor);
309             mark_join(hi_mark, anchored_before ? cursor->mark : cursor->anchor);
310         } else {
311             mark_move_beginning(lo_mark);
312             mark_move_end(hi_mark);
313         }
314         while (1) {
315             pcre_rc = 0;
316             if (mark_find_next_re(search_mark, regex, strlen(regex), &bline, &col, &char_count) == MLBUF_OK
317                 && (mark_move_to(search_mark, bline->line_index, col) == MLBUF_OK)
318                 && (mark_is_gte(search_mark, lo_mark))
319                 && (mark_is_lt(search_mark, hi_mark))
320                 && (!wrapped || mark_is_lt(search_mark, orig_mark))
321             ) {
322                 mark_move_to(search_mark_end, bline->line_index, col + char_count);
323                 mark_join(cursor->mark, search_mark);
324                 yn = NULL;
325                 if (all) {
326                     yn = MLE_PROMPT_YES;
327                 } else if (interactive) {
328                     highlight = srule_new_range(search_mark, search_mark_end, 0, TB_REVERSE);
329                     buffer_add_srule(cursor->bview->buffer, highlight);
330                     bview_rectify_viewport(cursor->bview);
331                     bview_draw(cursor->bview);
332                     editor_prompt(cursor->bview->editor, "replace: OK to replace? (y=yes, n=no, a=all, C-c=stop)",
333                         &(editor_prompt_params_t) { .kmap = cursor->bview->editor->kmap_prompt_yna }, &yn
334                     );
335                     buffer_remove_srule(cursor->bview->buffer, highlight);
336                     srule_destroy(highlight);
337                     bview_draw(cursor->bview);
338                 }
339                 if (!yn) {
340                     break;
341                 } else if (0 == strcmp(yn, MLE_PROMPT_YES) || 0 == strcmp(yn, MLE_PROMPT_ALL)) {
342                     str_append_replace_with_backrefs(&repl_backref, search_mark->bline->data, replacement, pcre_rc, pcre_ovector, 30);
343                     mark_replace_between_mark(search_mark, search_mark_end, repl_backref.data, repl_backref.len);
344                     str_free(&repl_backref);
345                     num_replacements += 1;
346                     if (0 == strcmp(yn, MLE_PROMPT_ALL)) all = 1;
347                 } else {
348                     mark_move_by(search_mark, 1);
349                 }
350             } else if (!wrapped) {
351                 mark_join(search_mark, lo_mark);
352                 wrapped = 1;
353             } else {
354                 break;
355             }
356         }
357     } while(0);
358 
359     if (cursor->is_anchored && lo_mark && hi_mark) {
360         mark_join(cursor->mark, anchored_before ? hi_mark : lo_mark);
361         mark_join(cursor->anchor, anchored_before ? lo_mark : hi_mark);
362     } else if (orig_mark) {
363         mark_join(cursor->mark, orig_mark);
364     }
365 
366     mark_set_pcre_capture(NULL, NULL, 0);
367     if (regex) free(regex);
368     if (replacement) free(replacement);
369     if (lo_mark) mark_destroy(lo_mark);
370     if (hi_mark) mark_destroy(hi_mark);
371     if (orig_mark) mark_destroy(orig_mark);
372     if (search_mark) mark_destroy(search_mark);
373     if (search_mark_end) mark_destroy(search_mark_end);
374 
375     if (interactive) {
376         MLE_SET_INFO(cursor->bview->editor, "replace: Replaced %d instance(s)", num_replacements);
377         if (orig_viewport_y >= 0) {
378             bview_set_viewport_y(cursor->bview, orig_viewport_y, 1);
379         } else {
380             bview_rectify_viewport(cursor->bview);
381         }
382         bview_draw(cursor->bview);
383     }
384 
385     return MLE_OK;
386 }
387