1 #include <ctype.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <inttypes.h>
6 #include "mle.h"
7 
8 #define MLE_MULTI_CURSOR_MARK_FN(pcursor, pfn, ...) do {\
9     cursor_t *cursor; \
10     DL_FOREACH((pcursor)->bview->cursors, cursor) { \
11         if (cursor->is_asleep) continue; \
12         pfn(cursor->mark, ##__VA_ARGS__); \
13     } \
14 } while(0)
15 
16 #define MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(pcursor, pfn) do {\
17     cursor_t *cursor; \
18     DL_FOREACH((pcursor)->bview->cursors, cursor) { \
19         if (cursor->is_asleep) continue; \
20         pfn(cursor->mark); \
21     } \
22 } while(0)
23 
24 #define MLE_MULTI_CURSOR_CODE(pcursor, pcode) do { \
25     cursor_t *cursor; \
26     DL_FOREACH((pcursor)->bview->cursors, cursor) { \
27         if (cursor->is_asleep) continue; \
28         pcode \
29     } \
30 } while(0)
31 
32 static void _cmd_force_redraw(cmd_context_t *ctx);
33 static int _cmd_pre_close(editor_t *editor, bview_t *bview);
34 static int _cmd_quit_inner(editor_t *editor, bview_t *bview);
35 static int _cmd_save(editor_t *editor, bview_t *bview, int save_as);
36 static int _cmd_search_next(bview_t *bview, cursor_t *cursor, mark_t *search_mark, char *regex, int regex_len);
37 static void _cmd_aproc_bview_passthru_cb(aproc_t *self, char *buf, size_t buf_len);
38 static void _cmd_isearch_prompt_cb(bview_t *bview, baction_t *action, void *udata);
39 static int _cmd_menu_browse_cb(cmd_context_t *ctx);
40 static int _cmd_menu_grep_cb(cmd_context_t *ctx);
41 static int _cmd_menu_ctag_cb(cmd_context_t *ctx);
42 static int _cmd_indent(cmd_context_t *ctx, int outdent);
43 static int _cmd_indent_line(bline_t *bline, int use_tabs, int outdent);
44 static void _cmd_help_inner(char *buf, kbinding_t *trie, str_t *h);
45 static void _cmd_insert_auto_indent_newline(cmd_context_t *ctx);
46 static void _cmd_insert_auto_indent_closing_bracket(cmd_context_t *ctx);
47 static void _cmd_shell_apply_cmd(cmd_context_t *ctx, char *cmd);
48 static void _cmd_get_input(cmd_context_t *ctx, kinput_t *ret_input);
49 static int _cmd_fsearch_inner(cmd_context_t *ctx, char *shell_cmd);
50 
51 // Insert data
cmd_insert_data(cmd_context_t * ctx)52 int cmd_insert_data(cmd_context_t *ctx) {
53     bint_t insertbuf_len;
54     char *insertbuf_cur;
55     bint_t len;
56     size_t insert_size;
57     kinput_t *input;
58     int i;
59 
60     // Ensure space in insertbuf
61     insert_size = MLE_MAX(6, ctx->bview->buffer->tab_width) * (ctx->pastebuf_len + 1);
62     if (ctx->editor->insertbuf_size < insert_size) {
63         ctx->editor->insertbuf = realloc(ctx->editor->insertbuf, insert_size);
64         memset(ctx->editor->insertbuf, 0, insert_size);
65         ctx->editor->insertbuf_size = insert_size;
66     }
67 
68     // Fill insertbuf... i=-1: ctx->input, i>=0: ctx->pastebuf
69     insertbuf_len = 0;
70     insertbuf_cur = ctx->editor->insertbuf;
71     for (i = -1; i == -1 || (size_t)i < ctx->pastebuf_len; i++) {
72         input = i == -1 ? &ctx->input : &ctx->pastebuf[i];
73         if (input->ch) {
74             len = utf8_unicode_to_char(insertbuf_cur, input->ch);
75         } else if (input->key == TB_KEY_ENTER || input->key == TB_KEY_CTRL_J || input->key == TB_KEY_CTRL_M) {
76             len = sprintf(insertbuf_cur, "\n");
77         } else if (input->key >= 0x20 && input->key <= 0x7e) {
78             len = sprintf(insertbuf_cur, "%c", input->key);
79         } else if (input->key == 0x09) {
80             if (ctx->bview->tab_to_space) {
81                 len = ctx->bview->buffer->tab_width - (ctx->cursor->mark->col % ctx->bview->buffer->tab_width);
82                 len = sprintf(insertbuf_cur, "%*c", (int)len, ' ');
83             } else {
84                 len = sprintf(insertbuf_cur, "\t");
85             }
86         } else {
87             len = 0; // TODO verbatim input
88         }
89         insertbuf_cur += len;
90         insertbuf_len += len;
91     }
92 
93     // Write insertbuf to buffer
94     if (insertbuf_len < 1) {
95         return MLE_ERR;
96     }
97     ctx->editor->insertbuf[insertbuf_len] = '\0';
98 
99     // Insert
100     if (insertbuf_len > 1
101         && ctx->editor->trim_paste
102         && memchr(ctx->editor->insertbuf, '\n', insertbuf_len) != NULL
103     ) {
104         // Insert with trim
105         char *trimmed = NULL;
106         int trimmed_len = 0;
107         util_pcre_replace("(?m) +$", ctx->editor->insertbuf, "", &trimmed, &trimmed_len);
108         MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_insert_before, trimmed, trimmed_len);
109         free(trimmed);
110     } else if (ctx->editor->auto_indent && !ctx->cursor->next && insertbuf_len == 1 && ctx->editor->insertbuf[0] == '\n') {
111         _cmd_insert_auto_indent_newline(ctx);
112     } else if (ctx->editor->auto_indent && !ctx->cursor->next && insertbuf_len == 1 && ctx->editor->insertbuf[0] == '}') {
113         _cmd_insert_auto_indent_closing_bracket(ctx);
114     } else {
115         // Insert without trim
116         MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_insert_before, ctx->editor->insertbuf, insertbuf_len);
117     }
118 
119     // Remember last insert data
120     str_set_len(&ctx->loop_ctx->last_insert, ctx->editor->insertbuf, insertbuf_len);
121 
122     return MLE_OK;
123 }
124 
125 // Insert newline above current line
cmd_insert_newline_above(cmd_context_t * ctx)126 int cmd_insert_newline_above(cmd_context_t *ctx) {
127     MLE_MULTI_CURSOR_CODE(ctx->cursor,
128         mark_move_bol(cursor->mark);
129         mark_insert_after(cursor->mark, "\n", 1);
130     );
131     return MLE_OK;
132 }
133 
134 // Delete char before cursor mark
cmd_delete_before(cmd_context_t * ctx)135 int cmd_delete_before(cmd_context_t *ctx) {
136     bint_t offset;
137     mark_get_offset(ctx->cursor->mark, &offset);
138     if (offset < 1) return MLE_OK;
139     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_delete_before, 1);
140     return MLE_OK;
141 }
142 
143 // Delete char after cursor mark
cmd_delete_after(cmd_context_t * ctx)144 int cmd_delete_after(cmd_context_t *ctx) {
145     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_delete_after, 1);
146     return MLE_OK;
147 }
148 
149 // Move cursor to beginning of line
cmd_move_bol(cmd_context_t * ctx)150 int cmd_move_bol(cmd_context_t *ctx) {
151     uint32_t ch;
152     mark_t *mark;
153     MLE_MULTI_CURSOR_CODE(ctx->cursor,
154         mark_clone(cursor->mark, &mark);
155         mark_move_bol(mark);
156         mark_get_char_after(mark, &ch);
157         if (isspace((char)ch)) {
158             mark_move_next_re(mark, "\\S", 2);
159         }
160         if (mark->col < cursor->mark->col) {
161             mark_join(cursor->mark, mark);
162         } else {
163             mark_move_bol(cursor->mark);
164         }
165         mark_destroy(mark);
166     );
167     bview_rectify_viewport(ctx->bview);
168     return MLE_OK;
169 }
170 
171 // Move cursor to end of line
cmd_move_eol(cmd_context_t * ctx)172 int cmd_move_eol(cmd_context_t *ctx) {
173     MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(ctx->cursor, mark_move_eol);
174     bview_rectify_viewport(ctx->bview);
175     return MLE_OK;
176 }
177 
178 // Move cursor to beginning of buffer
cmd_move_beginning(cmd_context_t * ctx)179 int cmd_move_beginning(cmd_context_t *ctx) {
180     MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(ctx->cursor, mark_move_beginning);
181     bview_rectify_viewport(ctx->bview);
182     return MLE_OK;
183 }
184 
185 // Move cursor to end of buffer
cmd_move_end(cmd_context_t * ctx)186 int cmd_move_end(cmd_context_t *ctx) {
187     MLE_MULTI_CURSOR_MARK_FN_NO_ARGS(ctx->cursor, mark_move_end);
188     bview_rectify_viewport(ctx->bview);
189     return MLE_OK;
190 }
191 
192 // Move cursor left one char
cmd_move_left(cmd_context_t * ctx)193 int cmd_move_left(cmd_context_t *ctx) {
194     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_by, -1);
195     bview_rectify_viewport(ctx->bview);
196     return MLE_OK;
197 }
198 
199 // Move cursor right one char
cmd_move_right(cmd_context_t * ctx)200 int cmd_move_right(cmd_context_t *ctx) {
201     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_by, 1);
202     bview_rectify_viewport(ctx->bview);
203     return MLE_OK;
204 }
205 
206 // Move cursor up one line
cmd_move_up(cmd_context_t * ctx)207 int cmd_move_up(cmd_context_t *ctx) {
208     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, -1);
209     bview_rectify_viewport(ctx->bview);
210     return MLE_OK;
211 }
212 
213 // Move cursor down one line
cmd_move_down(cmd_context_t * ctx)214 int cmd_move_down(cmd_context_t *ctx) {
215     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, 1);
216     bview_rectify_viewport(ctx->bview);
217     return MLE_OK;
218 }
219 
220 // Move cursor one page up
cmd_move_page_up(cmd_context_t * ctx)221 int cmd_move_page_up(cmd_context_t *ctx) {
222     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, -1 * ctx->bview->rect_buffer.h);
223     bview_zero_viewport_y(ctx->bview);
224     return MLE_OK;
225 }
226 
227 // Move cursor one page down
cmd_move_page_down(cmd_context_t * ctx)228 int cmd_move_page_down(cmd_context_t *ctx) {
229     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, ctx->bview->rect_buffer.h);
230     bview_zero_viewport_y(ctx->bview);
231     return MLE_OK;
232 }
233 
234 // Move to specific line
cmd_move_to_line(cmd_context_t * ctx)235 int cmd_move_to_line(cmd_context_t *ctx) {
236     char *linestr;
237     bint_t line;
238     editor_prompt(ctx->editor, "move_to_line: Line num?", NULL, &linestr);
239     if (!linestr) return MLE_OK;
240     line = strtoll(linestr, NULL, 10);
241     free(linestr);
242     if (line < 1) line = 1;
243     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_to, line - 1, 0);
244     bview_center_viewport_y(ctx->bview);
245     return MLE_OK;
246 }
247 
248 // Move vertically relative to current line
cmd_move_relative(cmd_context_t * ctx)249 int cmd_move_relative(cmd_context_t *ctx) {
250     int delta;
251     if (ctx->loop_ctx->numeric_params_len < 1) {
252         return MLE_ERR;
253     }
254     if (strcmp(ctx->static_param, "up") == 0) {
255         delta = -1;
256     } else if (strcmp(ctx->static_param, "down") == 0) {
257         delta = 1;
258     } else {
259         return MLE_ERR;
260     }
261     delta *= ctx->loop_ctx->numeric_params[0];
262     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_vert, delta);
263     return MLE_OK;
264 }
265 
266 // Move one word forward
cmd_move_word_forward(cmd_context_t * ctx)267 int cmd_move_word_forward(cmd_context_t *ctx) {
268     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_next_re_nudge, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1);
269     return MLE_OK;
270 }
271 
272 // Move one word back
cmd_move_word_back(cmd_context_t * ctx)273 int cmd_move_word_back(cmd_context_t *ctx) {
274     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_prev_re, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1);
275     return MLE_OK;
276 }
277 
278 // Move to next open bracket
cmd_move_bracket_forward(cmd_context_t * ctx)279 int cmd_move_bracket_forward(cmd_context_t *ctx) {
280     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_next_str_nudge, "{", 1);
281     bview_rectify_viewport(ctx->bview);
282     return MLE_OK;
283 }
284 
285 // Move to prev open bracket
cmd_move_bracket_back(cmd_context_t * ctx)286 int cmd_move_bracket_back(cmd_context_t *ctx) {
287     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_prev_str, "{", 1);
288     bview_rectify_viewport(ctx->bview);
289     return MLE_OK;
290 }
291 
292 // Move to matching bracket, or prev bracket if not on a bracket
cmd_move_bracket_toggle(cmd_context_t * ctx)293 int cmd_move_bracket_toggle(cmd_context_t *ctx) {
294     MLE_MULTI_CURSOR_CODE(ctx->cursor,
295         if (mark_move_bracket_pair(cursor->mark, ctx->buffer->byte_count) == MLBUF_ERR) {
296             mark_move_prev_re(cursor->mark, "[\\[\\(\\{]", strlen("[\\[\\(\\{]"));
297         }
298     );
299     bview_rectify_viewport(ctx->bview);
300     return MLE_OK;
301 }
302 
303 // Delete word back
cmd_delete_word_before(cmd_context_t * ctx)304 int cmd_delete_word_before(cmd_context_t *ctx) {
305     mark_t *tmark;
306     MLE_MULTI_CURSOR_CODE(ctx->cursor,
307         mark_clone(cursor->mark, &tmark);
308         mark_move_prev_re(tmark, MLE_RE_WORD_BACK, sizeof(MLE_RE_WORD_BACK)-1);
309         mark_delete_between_mark(cursor->mark, tmark);
310         mark_destroy(tmark);
311     );
312     return MLE_OK;
313 }
314 
315 // Delete word ahead
cmd_delete_word_after(cmd_context_t * ctx)316 int cmd_delete_word_after(cmd_context_t *ctx) {
317     mark_t *tmark;
318     MLE_MULTI_CURSOR_CODE(ctx->cursor,
319         mark_clone(cursor->mark, &tmark);
320         mark_move_next_re(tmark, MLE_RE_WORD_FORWARD, sizeof(MLE_RE_WORD_FORWARD)-1);
321         mark_delete_between_mark(cursor->mark, tmark);
322         mark_destroy(tmark);
323     );
324     return MLE_OK;
325 }
326 
327 // Toggle sel bound on cursors
cmd_toggle_anchor(cmd_context_t * ctx)328 int cmd_toggle_anchor(cmd_context_t *ctx) {
329     MLE_MULTI_CURSOR_CODE(ctx->cursor,
330         cursor_toggle_anchor(cursor, 1);
331     );
332     return MLE_OK;
333 }
334 
335 // Drop an is_asleep=1 cursor
cmd_drop_sleeping_cursor(cmd_context_t * ctx)336 int cmd_drop_sleeping_cursor(cmd_context_t *ctx) {
337     return bview_add_cursor_asleep(ctx->bview, ctx->cursor->mark->bline, ctx->cursor->mark->col, NULL);
338 }
339 
340 // Awake all is_asleep=1 cursors
cmd_wake_sleeping_cursors(cmd_context_t * ctx)341 int cmd_wake_sleeping_cursors(cmd_context_t *ctx) {
342     return bview_wake_sleeping_cursors(ctx->bview);
343 }
344 
345 // Remove all cursors except the active one
cmd_remove_extra_cursors(cmd_context_t * ctx)346 int cmd_remove_extra_cursors(cmd_context_t *ctx) {
347     return bview_remove_cursors_except(ctx->bview, ctx->cursor);
348 }
349 
350 // Drop column of cursors in selection
cmd_drop_cursor_column(cmd_context_t * ctx)351 int cmd_drop_cursor_column(cmd_context_t *ctx) {
352     bline_t *bline;
353     bint_t col;
354     mark_t *lo;
355     mark_t *hi;
356     MLE_MULTI_CURSOR_CODE(ctx->cursor,
357         if (!cursor->is_anchored) continue;
358         col = cursor->mark->col;
359         cursor_get_lo_hi(cursor, &lo, &hi);
360         for (bline = lo->bline; bline != hi->bline->next; bline = bline->next) {
361             MLBUF_BLINE_ENSURE_CHARS(bline);
362             if ((bline == lo->bline && col < lo->col)
363                 || (bline == hi->bline && col > hi->col)
364                 || col > bline->char_count
365             ) {
366                 continue;
367             }
368             if (bline != cursor->mark->bline || col != cursor->mark->col) {
369                 bview_add_cursor(ctx->bview, bline, col, NULL);
370             }
371         }
372         cursor_toggle_anchor(cursor, 1);
373     );
374     return MLE_OK;
375 }
376 
377 // Search for a regex
cmd_search(cmd_context_t * ctx)378 int cmd_search(cmd_context_t *ctx) {
379     char *regex;
380     int regex_len;
381     mark_t *search_mark;
382     editor_prompt(ctx->editor, "search: Regex?", NULL, &regex);
383     if (!regex) return MLE_OK;
384     regex_len = strlen(regex);
385     search_mark = buffer_add_mark(ctx->bview->buffer, NULL, 0);
386     MLE_MULTI_CURSOR_CODE(ctx->cursor,
387         _cmd_search_next(ctx->bview, cursor, search_mark, regex, regex_len);
388     );
389     mark_destroy(search_mark);
390     if (ctx->bview->last_search) free(ctx->bview->last_search);
391     ctx->bview->last_search = regex;
392     return MLE_OK;
393 }
394 
395 // Search for next instance of last search regex
cmd_search_next(cmd_context_t * ctx)396 int cmd_search_next(cmd_context_t *ctx) {
397     int regex_len;
398     mark_t *search_mark;
399     if (!ctx->bview->last_search) return MLE_OK;
400     regex_len = strlen(ctx->bview->last_search);
401     search_mark = buffer_add_mark(ctx->bview->buffer, NULL, 0);
402     MLE_MULTI_CURSOR_CODE(ctx->cursor,
403         _cmd_search_next(ctx->bview, cursor, search_mark, ctx->bview->last_search, regex_len);
404     );
405     mark_destroy(search_mark);
406     return MLE_OK;
407 }
408 
409 // Interactive search and replace
cmd_replace(cmd_context_t * ctx)410 int cmd_replace(cmd_context_t *ctx) {
411     return cursor_replace(ctx->cursor, 1, NULL, NULL);
412 }
413 
414 // Redraw screen
cmd_redraw(cmd_context_t * ctx)415 int cmd_redraw(cmd_context_t *ctx) {
416     bview_center_viewport_y(ctx->bview);
417     _cmd_force_redraw(ctx);
418     return MLE_OK;
419 }
420 
421 // Zero viewport y
cmd_viewport_top(cmd_context_t * ctx)422 int cmd_viewport_top(cmd_context_t *ctx) {
423     bview_zero_viewport_y(ctx->bview);
424     return MLE_OK;
425 }
426 
427 // Center viewport y
cmd_viewport_mid(cmd_context_t * ctx)428 int cmd_viewport_mid(cmd_context_t *ctx) {
429     bview_center_viewport_y(ctx->bview);
430     return MLE_OK;
431 }
432 
433 // Max viewport y
cmd_viewport_bot(cmd_context_t * ctx)434 int cmd_viewport_bot(cmd_context_t *ctx) {
435     bview_max_viewport_y(ctx->bview);
436     return MLE_OK;
437 }
438 
439 // Toggle between top and mid viewport y
cmd_viewport_toggle(cmd_context_t * ctx)440 int cmd_viewport_toggle(cmd_context_t *ctx) {
441     bline_t *orig;
442     bline_t *mid;
443     bline_t *top;
444     orig = ctx->bview->viewport_bline;
445     cmd_viewport_mid(ctx); mid = ctx->bview->viewport_bline;
446     cmd_viewport_top(ctx); top = ctx->bview->viewport_bline;
447     if (mid == orig) {
448         cmd_viewport_top(ctx);
449     } else if (top == orig) {
450         cmd_viewport_bot(ctx);
451     } else {
452         cmd_viewport_mid(ctx);
453     }
454     return MLE_OK;
455 }
456 
457 // Find next occurence of word under cursor
cmd_find_word(cmd_context_t * ctx)458 int cmd_find_word(cmd_context_t *ctx) {
459     char *re;
460     char *word;
461     bint_t re_len;
462     bint_t word_len;
463     MLE_MULTI_CURSOR_CODE(ctx->cursor,
464         if (cursor_select_by(cursor, "word", 0) == MLE_OK) {
465             mark_get_between_mark(cursor->mark, cursor->anchor, &word, &word_len);
466             re_len = asprintf(&re, "\\b%s\\b", word);
467             free(word);
468             cursor_toggle_anchor(cursor, 0);
469             if (mark_move_next_re(cursor->mark, re, re_len) == MLBUF_ERR) {
470                 mark_move_beginning(cursor->mark);
471                 mark_move_next_re(cursor->mark, re, re_len);
472             }
473             free(re);
474         }
475     );
476     bview_rectify_viewport(ctx->bview);
477     return MLE_OK;
478 }
479 
480 // Incremental search
cmd_isearch(cmd_context_t * ctx)481 int cmd_isearch(cmd_context_t *ctx) {
482     editor_prompt(ctx->editor, "isearch: Regex?", &(editor_prompt_params_t) {
483         .kmap = ctx->editor->kmap_prompt_isearch,
484         .prompt_cb = _cmd_isearch_prompt_cb
485     }, NULL);
486     if (ctx->bview->isearch_rule) {
487         buffer_remove_srule(ctx->bview->buffer, ctx->bview->isearch_rule);
488         srule_destroy(ctx->bview->isearch_rule);
489         ctx->bview->isearch_rule = NULL;
490     }
491     return MLE_OK;
492 }
493 
494 // Cut text
cmd_cut(cmd_context_t * ctx)495 int cmd_cut(cmd_context_t *ctx) {
496     int append;
497     append = ctx->loop_ctx->last_cmd && ctx->loop_ctx->last_cmd->func == cmd_cut ? 1 : 0;
498     MLE_MULTI_CURSOR_CODE(ctx->cursor,
499         cursor_cut_copy(cursor, 1, 1, append);
500     );
501     return MLE_OK;
502 }
503 
504 // Copy text
cmd_copy(cmd_context_t * ctx)505 int cmd_copy(cmd_context_t *ctx) {
506     MLE_MULTI_CURSOR_CODE(ctx->cursor,
507         cursor_cut_copy(cursor, 0, 1, 0);
508     );
509     return MLE_OK;
510 }
511 
512 // Paste text
cmd_uncut(cmd_context_t * ctx)513 int cmd_uncut(cmd_context_t *ctx) {
514     MLE_MULTI_CURSOR_CODE(ctx->cursor,
515         cursor_uncut(cursor);
516     );
517     return MLE_OK;
518 }
519 
520 // Copy in between chars
cmd_copy_by(cmd_context_t * ctx)521 int cmd_copy_by(cmd_context_t *ctx) {
522     MLE_MULTI_CURSOR_CODE(ctx->cursor,
523         if (cursor_select_by(cursor, ctx->static_param, 0) == MLE_OK) {
524             cursor_cut_copy(cursor, 0, 0, 0);
525         }
526     );
527     return MLE_OK;
528 }
529 
530 // Cut in between chars
cmd_cut_by(cmd_context_t * ctx)531 int cmd_cut_by(cmd_context_t *ctx) {
532     MLE_MULTI_CURSOR_CODE(ctx->cursor,
533         if (cursor_select_by(cursor, ctx->static_param, 0) == MLE_OK) {
534             cursor_cut_copy(cursor, 1, 0, 0);
535         }
536     );
537     return MLE_OK;
538 }
539 
540 // Anchor between chars
cmd_anchor_by(cmd_context_t * ctx)541 int cmd_anchor_by(cmd_context_t *ctx) {
542     MLE_MULTI_CURSOR_CODE(ctx->cursor,
543         cursor_select_by(cursor, ctx->static_param, 1);
544     );
545     return MLE_OK;
546 }
547 
548 // Go to next bview
cmd_next(cmd_context_t * ctx)549 int cmd_next(cmd_context_t *ctx) {
550     editor_set_active(ctx->editor, ctx->bview->all_next);
551     return MLE_OK;
552 }
553 
554 // Go to prev bview
cmd_prev(cmd_context_t * ctx)555 int cmd_prev(cmd_context_t *ctx) {
556     editor_set_active(ctx->editor, ctx->bview->all_prev);
557     return MLE_OK;
558 }
559 
560 // Split a bview vertically
cmd_split_vertical(cmd_context_t * ctx)561 int cmd_split_vertical(cmd_context_t *ctx) {
562     bview_t *child;
563     if (bview_split(ctx->bview, 1, 0.5, &child) == MLE_OK) {
564         editor_set_active(ctx->editor, child);
565     }
566     return MLE_OK;
567 }
568 
569 // Split a bview horizontally
cmd_split_horizontal(cmd_context_t * ctx)570 int cmd_split_horizontal(cmd_context_t *ctx) {
571     bview_t *child;
572     if (bview_split(ctx->bview, 0, 0.5, &child) == MLE_OK) {
573         editor_set_active(ctx->editor, child);
574     }
575     return MLE_OK;
576 }
577 
578 // Fuzzy path search via fzf
cmd_fsearch(cmd_context_t * ctx)579 int cmd_fsearch(cmd_context_t *ctx) {
580     return _cmd_fsearch_inner(ctx, "fzf");
581 }
582 
583 // Fuzzy path search via fzy
cmd_fsearch_fzy(cmd_context_t * ctx)584 int cmd_fsearch_fzy(cmd_context_t *ctx) {
585     char shell_cmd[32];
586     int nlines;
587     nlines = tb_height();
588     nlines = MLE_MAX(MLE_MIN(nlines, 9999), 3);
589     sprintf(shell_cmd, "find . -type f | fzy -l %d", nlines);
590     return _cmd_fsearch_inner(ctx, shell_cmd);
591 }
592 
593 // Grep for pattern in cwd
cmd_grep(cmd_context_t * ctx)594 int cmd_grep(cmd_context_t *ctx) {
595     aproc_t *aproc;
596     char *path;
597     char *path_arg;
598     char *cmd;
599     char *grep_fmt;
600     editor_prompt(ctx->editor, "grep: Pattern?", NULL, &path);
601     if (!path) return MLE_OK;
602     if (ctx->static_param) {
603         grep_fmt = ctx->static_param;
604     } else {
605         grep_fmt = "grep --color=never -P -i -I -n -r %s . 2>/dev/null";
606     }
607     path_arg = util_escape_shell_arg(path, strlen(path));
608     free(path);
609     asprintf(&cmd, grep_fmt, path_arg);
610     free(path_arg);
611     if (!cmd) {
612         MLE_RETURN_ERR(ctx->editor, "Failed to format grep cmd: %s", grep_fmt);
613     }
614 
615     aproc = aproc_new(ctx->editor, ctx->bview, &(ctx->bview->aproc), cmd, 0, _cmd_aproc_bview_passthru_cb);
616     free(cmd);
617     if (!aproc) return MLE_ERR;
618     editor_menu(ctx->editor, _cmd_menu_grep_cb, NULL, 0, aproc, NULL);
619     return MLE_OK;
620 }
621 
622 // Invoke ctag search
cmd_ctag(cmd_context_t * ctx)623 int cmd_ctag(cmd_context_t *ctx) {
624     aproc_t *aproc;
625     char *word;
626     char *word_arg;
627     char *cmd;
628     bint_t word_len;
629     if (cursor_select_by(ctx->cursor, "word", 0) != MLE_OK) {
630         MLE_RETURN_ERR(ctx->editor, "%s", "Failed to select word under cursor");
631     }
632     mark_get_between_mark(ctx->cursor->mark, ctx->cursor->anchor, &word, &word_len);
633     cursor_toggle_anchor(ctx->cursor, 0);
634     word_arg = util_escape_shell_arg(word, word_len);
635     free(word);
636     asprintf(&cmd, "readtags -e - %s 2>/dev/null", word_arg);
637     free(word_arg);
638     if (!cmd) {
639         MLE_RETURN_ERR(ctx->editor, "%s", "Failed to format readtags cmd");
640     }
641     aproc = aproc_new(ctx->editor, ctx->bview, &(ctx->bview->aproc), cmd, 0, _cmd_aproc_bview_passthru_cb);
642     free(cmd);
643     if (!aproc) return MLE_ERR;
644     editor_menu(ctx->editor, _cmd_menu_ctag_cb, NULL, 0, aproc, NULL);
645     return MLE_OK;
646 }
647 
648 // Browse directory via tree
cmd_browse(cmd_context_t * ctx)649 int cmd_browse(cmd_context_t *ctx) {
650     bview_t *menu;
651     aproc_t *aproc;
652     char *cmd;
653     asprintf(&cmd, "tree --charset C -n -f -L 2 %s 2>/dev/null", ctx->static_param ? ctx->static_param : "");
654     aproc = aproc_new(ctx->editor, ctx->bview, &(ctx->bview->aproc), cmd, 0, _cmd_aproc_bview_passthru_cb);
655     free(cmd);
656     if (!aproc) return MLE_ERR;
657     editor_menu(ctx->editor, _cmd_menu_browse_cb, "..\n", 3, aproc, &menu);
658     mark_move_beginning(menu->active_cursor->mark);
659     return MLE_OK;
660 }
661 
662 // Save-as file
cmd_save_as(cmd_context_t * ctx)663 int cmd_save_as(cmd_context_t *ctx) {
664     _cmd_save(ctx->editor, ctx->bview, 1);
665     return MLE_OK;
666 }
667 
668 // Save file
cmd_save(cmd_context_t * ctx)669 int cmd_save(cmd_context_t *ctx) {
670     _cmd_save(ctx->editor, ctx->bview, 0);
671     return MLE_OK;
672 }
673 
674 // Open file in a new bview
cmd_open_file(cmd_context_t * ctx)675 int cmd_open_file(cmd_context_t *ctx) {
676     char *path;
677     editor_prompt(ctx->editor, "new_open: File?", NULL, &path);
678     if (!path) return MLE_OK;
679     editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, path, strlen(path), 1, 0, 0, NULL, NULL);
680     free(path);
681     return MLE_OK;
682 }
683 
684 // Open empty buffer in a new bview
cmd_open_new(cmd_context_t * ctx)685 int cmd_open_new(cmd_context_t *ctx) {
686     editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, NULL);
687     return MLE_OK;
688 }
689 
690 // Open file into current bview
cmd_open_replace_file(cmd_context_t * ctx)691 int cmd_open_replace_file(cmd_context_t *ctx) {
692     char *path;
693     if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_ERR) return MLE_OK;
694     path = NULL;
695     editor_prompt(ctx->editor, "replace_open: Path?", NULL, &path);
696     if (!path) return MLE_OK;
697     bview_open(ctx->bview, path, strlen(path));
698     free(path);
699     return MLE_OK;
700 }
701 
702 // Open empty buffer into current bview
cmd_open_replace_new(cmd_context_t * ctx)703 int cmd_open_replace_new(cmd_context_t *ctx) {
704     if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_ERR) return MLE_OK;
705     bview_open(ctx->bview, NULL, 0);
706     return MLE_OK;
707 }
708 
709 // Close bview
cmd_close(cmd_context_t * ctx)710 int cmd_close(cmd_context_t *ctx) {
711     int num_open;
712     int num_closed;
713     if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_ERR) return MLE_OK;
714     num_open = editor_bview_edit_count(ctx->editor);
715     editor_close_bview(ctx->editor, ctx->bview, &num_closed);
716     ctx->loop_ctx->should_exit = num_closed == num_open ? 1 : 0;
717     return MLE_OK;
718 }
719 
720 // Quit editor
cmd_quit(cmd_context_t * ctx)721 int cmd_quit(cmd_context_t *ctx) {
722     bview_t *bview;
723     bview_t *tmp;
724     if (ctx->editor->loop_depth > 1) return MLE_OK;
725     DL_FOREACH_SAFE2(ctx->editor->top_bviews, bview, tmp, top_next) {
726         if (!MLE_BVIEW_IS_EDIT(bview)) {
727             continue;
728         } else if (_cmd_quit_inner(ctx->editor, bview) == MLE_ERR) {
729             return MLE_OK;
730         }
731     }
732     ctx->loop_ctx->should_exit = 1;
733     return MLE_OK;
734 }
735 
736 // Quit editor without saving
cmd_quit_without_saving(cmd_context_t * ctx)737 int cmd_quit_without_saving(cmd_context_t *ctx) {
738     ctx->loop_ctx->should_exit = 1;
739     return MLE_OK;
740 }
741 
742 // Apply a macro with single-char name
cmd_apply_macro_by(cmd_context_t * ctx)743 int cmd_apply_macro_by(cmd_context_t *ctx) {
744     kmacro_t *macro;
745     uint32_t ch;
746     char name[6] = { 0 };
747     if (ctx->editor->macro_apply) MLE_RETURN_ERR(ctx->editor, "Cannot nest macros%s", "");
748     ch = MLE_PARAM_WILDCARD(ctx, 0);
749     if (!ch) return MLE_OK;
750     utf8_unicode_to_char(name, ch);
751     HASH_FIND_STR(ctx->editor->macro_map, name, macro);
752     if (!macro) MLE_RETURN_ERR(ctx->editor, "Macro not found with name '%s'", name);
753     ctx->editor->macro_apply = macro;
754     ctx->editor->macro_apply_input_index = 0;
755     return MLE_OK;
756 }
757 
758 
759 // Apply a macro
cmd_apply_macro(cmd_context_t * ctx)760 int cmd_apply_macro(cmd_context_t *ctx) {
761     char *name;
762     kmacro_t *macro;
763     if (ctx->editor->macro_apply) MLE_RETURN_ERR(ctx->editor, "Cannot nest macros%s", "");
764     editor_prompt(ctx->editor, "apply_macro: Name?", NULL, &name);
765     if (!name) return MLE_OK;
766     HASH_FIND_STR(ctx->editor->macro_map, name, macro);
767     free(name);
768     if (!macro) MLE_RETURN_ERR(ctx->editor, "Macro not found%s", "");
769     ctx->editor->macro_apply = macro;
770     ctx->editor->macro_apply_input_index = 0;
771     return MLE_OK;
772 }
773 
774 // No-op
cmd_noop(cmd_context_t * ctx)775 int cmd_noop(cmd_context_t *ctx) {
776     (void)ctx;
777     return MLE_OK;
778 }
779 
780 // Move forward til a certain char
cmd_move_until_forward(cmd_context_t * ctx)781 int cmd_move_until_forward(cmd_context_t *ctx) {
782     uint32_t ch;
783     char str[6] = { 0 };
784     ch = MLE_PARAM_WILDCARD(ctx, 0);
785     if (!ch) return MLE_OK;
786     utf8_unicode_to_char(str, ch);
787     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_next_str, str, strlen(str));
788     bview_rectify_viewport(ctx->bview);
789     return MLE_OK;
790 }
791 
792 // Move back til a certain char
cmd_move_until_back(cmd_context_t * ctx)793 int cmd_move_until_back(cmd_context_t *ctx) {
794     uint32_t ch;
795     char str[6] = { 0 };
796     ch = MLE_PARAM_WILDCARD(ctx, 0);
797     if (!ch) return MLE_OK;
798     utf8_unicode_to_char(str, ch);
799     MLE_MULTI_CURSOR_MARK_FN(ctx->cursor, mark_move_prev_str, str, strlen(str));
800     bview_rectify_viewport(ctx->bview);
801     return MLE_OK;
802 }
803 
804 // Undo
cmd_undo(cmd_context_t * ctx)805 int cmd_undo(cmd_context_t *ctx) {
806     buffer_undo(ctx->bview->buffer);
807     return MLE_OK;
808 }
809 
810 // Redo
cmd_redo(cmd_context_t * ctx)811 int cmd_redo(cmd_context_t *ctx) {
812     buffer_redo(ctx->bview->buffer);
813     return MLE_OK;
814 }
815 
816 // Indent line(s)
cmd_indent(cmd_context_t * ctx)817 int cmd_indent(cmd_context_t *ctx) {
818     return _cmd_indent(ctx, 0);
819 }
820 
821 // Outdent line(s)
cmd_outdent(cmd_context_t * ctx)822 int cmd_outdent(cmd_context_t *ctx) {
823     return _cmd_indent(ctx, 1);
824 }
825 
826 // Set option
cmd_set_opt(cmd_context_t * ctx)827 int cmd_set_opt(cmd_context_t *ctx) {
828     char *prompt;
829     char *val;
830     int vali;
831     if (!ctx->static_param) return MLE_ERR;
832     asprintf(&prompt, "set_opt: %s?", ctx->static_param);
833     editor_prompt(ctx->editor, prompt, NULL, &val);
834     free(prompt);
835     if (!val) return MLE_OK;
836     vali = atoi(val);
837     if (strcmp(ctx->static_param, "tab_to_space") == 0) {
838         ctx->bview->tab_to_space = vali ? 1 : 0;
839     } else if (strcmp(ctx->static_param, "tab_width") == 0) {
840         ctx->bview->tab_width = MLE_MAX(vali, 1);
841         buffer_set_tab_width(ctx->bview->buffer, ctx->bview->tab_width);
842     } else if (strcmp(ctx->static_param, "syntax") == 0) {
843         bview_set_syntax(ctx->bview, val);
844         buffer_apply_styles(ctx->bview->buffer, ctx->bview->buffer->first_line, ctx->bview->buffer->line_count);
845     } else if (strcmp(ctx->static_param, "soft_wrap") == 0) {
846         ctx->editor->soft_wrap = vali ? 1 : 0;
847     }
848     return MLE_OK;
849 }
850 
851 // Shell
cmd_shell(cmd_context_t * ctx)852 int cmd_shell(cmd_context_t *ctx) {
853     char *cmd;
854 
855     // Get shell cmd
856     if (ctx->static_param) {
857         cmd = strdup(ctx->static_param);
858     } else {
859         editor_prompt(ctx->editor, "shell: Cmd?", NULL, &cmd);
860         if (!cmd) return MLE_OK;
861     }
862 
863     // Apply shell cmd
864     _cmd_shell_apply_cmd(ctx, cmd);
865 
866     free(cmd);
867     return MLE_OK;
868 }
869 
870 // Perl
cmd_perl(cmd_context_t * ctx)871 int cmd_perl(cmd_context_t *ctx) {
872     char *code;
873     char *code_escaped;
874     char *cmd;
875 
876     // Get perl code
877     if (ctx->static_param) {
878         code = strdup(ctx->static_param);
879     } else {
880         editor_prompt(ctx->editor, "perl: Code?", NULL, &code);
881         if (!code) return MLE_OK;
882     }
883 
884     // Shell escape code
885     code_escaped = util_escape_shell_arg(code, strlen(code));
886     free(code);
887 
888     // Format cmd
889     asprintf(&cmd, "perl -lp -E 'BEGIN{$i=0}' -E %s 2>/dev/null", code_escaped);
890     free(code_escaped);
891 
892     // Apply perl cmd
893     _cmd_shell_apply_cmd(ctx, cmd);
894 
895     free(cmd);
896     return MLE_OK;
897 }
898 
899 // Jump to a `\S{2,}` on screen via `[a-z][a-z]`
cmd_jump(cmd_context_t * ctx)900 int cmd_jump(cmd_context_t *ctx) {
901     bline_t *bline;
902     bint_t col;
903     bint_t nchars;
904     bint_t stop_line_index;
905     mark_t *mark;
906     mark_t *jumps;
907     int jumpi, jumpt, screen_x, screen_y;
908     char jumpa[3];
909     kinput_t ev[2];
910     int headless;
911 
912     // Check if headless
913     headless = ctx->editor->headless_mode ? 1 : 0;
914 
915     // Set boundaries
916     mark_clone(ctx->cursor->mark, &mark);
917     if (headless) {
918         mark_move_bol(mark);
919         stop_line_index = mark->bline->line_index + 1;
920     } else {
921         mark_move_to_w_bline(mark, ctx->bview->viewport_bline, 0);
922         stop_line_index = ctx->bview->viewport_bline->line_index + ctx->bview->rect_buffer.h;
923     }
924 
925     // Make jump map
926     jumps = calloc(26*26, sizeof(mark_t));
927     jumpi = 0;
928 
929     do {
930         // Loop for words
931         while (jumpi < 26*26 && mark_move_next_re_ex(mark, "\\S{2,}", strlen("\\S{2,}"), &bline, &col, &nchars) == MLBUF_OK) {
932             if (bline->line_index >= stop_line_index) break;
933             jumps[jumpi].bline = bline;
934             jumps[jumpi].col = col;
935             mark_move_by(mark, MLE_MAX(0, nchars - 2));
936             if (!headless) {
937                 bview_get_screen_coords(ctx->bview, mark, &screen_x, &screen_y, NULL);
938                 sprintf(jumpa, "%c%c", 'a' + (jumpi / 26), 'a' + (jumpi % 26));
939                 tb_print(screen_x, screen_y, TB_WHITE | TB_BOLD, TB_MAGENTA, jumpa);
940             }
941             jumpi += 1;
942             mark_move_by(mark, 2);
943         }
944         if (jumpi < 1) break;
945         if (!headless) tb_present();
946 
947         // Get 2 inputs
948         _cmd_get_input(ctx, &ev[0]); if (ev[0].ch < 'a' || ev[0].ch > 'z') break;
949         _cmd_get_input(ctx, &ev[1]);
950 
951         // Jump
952         jumpt = ((ev[0].ch - 'a') * 26) + (ev[1].ch - 'a');
953         if (jumpt >= 0 && jumpt < jumpi) {
954             mark_move_to_w_bline(ctx->cursor->mark, jumps[jumpt].bline, jumps[jumpt].col);
955         }
956     } while(0);
957 
958     // Cleanup
959     free(jumps);
960     mark_destroy(mark);
961     return MLE_OK;
962 }
963 
964 // Force a redraw of the screen
_cmd_force_redraw(cmd_context_t * ctx)965 static void _cmd_force_redraw(cmd_context_t *ctx) {
966     int w;
967     int h;
968     int x;
969     int y;
970     (void)ctx;
971     if (tb_width() >= 0) tb_shutdown();
972     tb_init();
973     tb_select_input_mode(TB_INPUT_ALT);
974     tb_set_cursor(-1, -1);
975     w = tb_width();
976     h = tb_height();
977     for (x = 0; x < w; x++) {
978         for (y = 0; y < h; y++) {
979             tb_change_cell(x, y, 160, 0, 0);
980         }
981     }
982     tb_present();
983 }
984 
985 // Indent or outdent line(s)
_cmd_indent(cmd_context_t * ctx,int outdent)986 static int _cmd_indent(cmd_context_t *ctx, int outdent) {
987     bline_t *start;
988     bline_t *end;
989     bline_t *cur;
990     int use_tabs;
991     use_tabs = ctx->bview->tab_to_space ? 0 : 1;
992     MLE_MULTI_CURSOR_CODE(ctx->cursor,
993         start = ctx->cursor->mark->bline;
994         if (ctx->cursor->is_anchored) {
995             end = ctx->cursor->anchor->bline;
996             if (start->line_index > end->line_index) {
997                 cur = end;
998                 end = start;
999                 start = cur;
1000             }
1001         } else {
1002             end = start;
1003         }
1004         ctx->buffer->is_style_disabled++;
1005         for (cur = start; cur != end->next; cur = cur->next) {
1006             _cmd_indent_line(cur, use_tabs, outdent);
1007         }
1008         ctx->buffer->is_style_disabled--;
1009         buffer_apply_styles(ctx->buffer, start, 0);
1010     );
1011     return MLE_OK;
1012 }
1013 
1014 // Push kmap
cmd_push_kmap(cmd_context_t * ctx)1015 int cmd_push_kmap(cmd_context_t *ctx) {
1016     kmap_t *kmap;
1017     char *kmap_name;
1018     int rv;
1019 
1020     // Get kmap name
1021     kmap_name = NULL;
1022     if (ctx->static_param) {
1023         kmap_name = strdup(ctx->static_param);
1024     } else {
1025         editor_prompt(ctx->editor, "push_kmap: Kmap name?", NULL, &kmap_name);
1026         if (!kmap_name) return MLE_OK;
1027     }
1028 
1029     // Find kmap by name
1030     kmap = NULL;
1031     HASH_FIND_STR(ctx->editor->kmap_map, kmap_name, kmap);
1032 
1033     if (!kmap) {
1034         // Not found
1035         MLE_SET_ERR(ctx->editor, "push_kmap: No kmap defined '%s'", kmap_name);
1036         rv = MLE_ERR;
1037     } else {
1038         // Found, push
1039         bview_push_kmap(ctx->bview, kmap);
1040         rv = MLE_OK;
1041     }
1042 
1043     free(kmap_name);
1044     return rv;
1045 }
1046 
1047 // Pop kmap
cmd_pop_kmap(cmd_context_t * ctx)1048 int cmd_pop_kmap(cmd_context_t *ctx) {
1049     bview_pop_kmap(ctx->bview, NULL);
1050     return MLE_OK;
1051 }
1052 
1053 // Hacky as hell less integration
cmd_less(cmd_context_t * ctx)1054 int cmd_less(cmd_context_t *ctx) {
1055     int rc;
1056     char *sh_fmt;
1057     char *sh;
1058     char out[32];
1059     int screen_x;
1060     int screen_y;
1061     ssize_t out_len;
1062     bint_t line_top;
1063     char tmp_buf[32];
1064     char tmp_linenum[32];
1065     int tmp_buf_fd;
1066     int tmp_linenum_fd;
1067 
1068     rc = MLE_OK;
1069     sh = NULL;
1070     tmp_buf_fd = -1;
1071     tmp_linenum_fd = -1;
1072 
1073     do {
1074         if (MLE_ERR == bview_get_screen_coords(ctx->bview, ctx->cursor->mark, &screen_x, &screen_y, NULL)) {
1075             rc = MLE_ERR;
1076             break;
1077         }
1078         sprintf(tmp_linenum, "/tmp/mle-less-XXXXXX");
1079         if ((tmp_linenum_fd = mkstemp(tmp_linenum)) < 0) {
1080             rc = MLE_ERR;
1081             break;
1082         }
1083         sprintf(tmp_buf, "/tmp/mle-less-XXXXXX");
1084         if ((tmp_buf_fd = mkstemp(tmp_buf)) < 0) {
1085             rc = MLE_ERR;
1086             break;
1087         }
1088         if (MLBUF_ERR == buffer_write_to_fd(ctx->buffer, tmp_buf_fd, NULL)) {
1089             rc = MLE_ERR;
1090             break;
1091         }
1092         sh_fmt = "tmp_lesskey=$(mktemp -q /tmp/mle-less-XXXXXX);"
1093             "echo -e \"#command\\nq visual\\nQ visual\\n:q visual\\n:Q visual\\nZZ visual\\n#env\\n"
1094             "LESSEDIT=echo %%lt >%s; kill 0\" | lesskey -o $tmp_lesskey -- -;"
1095             "less +%ld -j%ld -k $tmp_lesskey -SN %s;"
1096             "rm -f $tmp_lesskey";
1097         asprintf(&sh, sh_fmt, tmp_linenum, ctx->cursor->mark->bline->line_index+1, screen_y+1, tmp_buf);
1098         tb_shutdown();
1099         if (MLE_ERR == util_shell_exec(ctx->editor, sh, -1, NULL, 0, 1, "bash", NULL, NULL)) {
1100             rc = MLE_ERR;
1101             break;
1102         }
1103         _cmd_force_redraw(ctx);
1104         out_len = read(tmp_linenum_fd, &out, sizeof(out)-1);
1105         out[out_len >= 0 ? out_len : 0] = '\0';
1106         line_top = (bint_t)strtoull(out, NULL, 10);
1107         if (line_top <= 0) {
1108             rc = MLE_ERR;
1109             break;
1110         }
1111         mark_move_to(ctx->cursor->mark, (line_top) + ctx->bview->rect_buffer.h/2, 0);
1112         bview_center_viewport_y(ctx->bview);
1113     } while(0);
1114 
1115     if (tmp_buf_fd >= 0) {
1116         close(tmp_buf_fd);
1117         unlink(tmp_buf);
1118     }
1119     if (tmp_linenum_fd >= 0) {
1120         close(tmp_linenum_fd);
1121         unlink(tmp_linenum);
1122     }
1123     if (sh) free(sh);
1124 
1125     return rc;
1126 }
1127 
1128 // Show help
cmd_show_help(cmd_context_t * ctx)1129 int cmd_show_help(cmd_context_t *ctx) {
1130     str_t h = {0}; // help
1131     kmap_t *kmap;
1132     kmap_t *kmap_tmp;
1133     kmap_node_t *kmap_node;
1134     int kmap_node_depth;
1135     int i;
1136     bview_t *bview;
1137     char buf[1024];
1138 
1139     str_append(&h,
1140         "# mle command help\n\n"
1141         "    notes\n"
1142         "        C- means Ctrl\n"
1143         "        M- means Alt\n"
1144         "        cmd_x=(default) means unmatched input is handled by cmd_x\n"
1145         "        (allow_fallthru)=yes means unmatched input is handled by the parent kmap\n\n"
1146     );
1147 
1148     // Build current kmap stack
1149     str_append(&h, "    mode stack\n");
1150     kmap_node_depth = 0;
1151     DL_FOREACH(ctx->bview->kmap_stack, kmap_node) {
1152         str_append(&h, "        ");
1153         for (i = 0; i < kmap_node_depth; i++) {
1154             str_append(&h, "    ");
1155         }
1156         if (i > 0) {
1157             str_append(&h, "\\_ ");
1158         }
1159         str_append(&h, kmap_node->kmap->name);
1160         if (kmap_node == ctx->bview->kmap_tail) {
1161             str_append(&h, " (current)");
1162         }
1163         str_append(&h, "\n");
1164         kmap_node_depth += 1;
1165     }
1166     str_append(&h, "\n");
1167 
1168     // Build kmap bindings
1169     HASH_ITER(hh, ctx->editor->kmap_map, kmap, kmap_tmp) {
1170         str_append(&h, "    mode ");
1171         str_append(&h, kmap->name);
1172         str_append(&h, "\n");
1173         snprintf(buf, sizeof(buf), "        %-40s %-16s\n", "(allow_fallthru)", kmap->allow_fallthru ? "yes" : "no");
1174         str_append(&h, buf);
1175         if (kmap->default_cmd_name) {
1176             snprintf(buf, sizeof(buf), "        %-40s %-16s\n", kmap->default_cmd_name, "(default)");
1177             str_append(&h, buf);
1178         }
1179         _cmd_help_inner(buf, kmap->bindings->children, &h);
1180         str_append(&h, "\n");
1181     }
1182 
1183     // Show help in new bview
1184     editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, &bview);
1185     buffer_insert(bview->buffer, 0, h.data, (bint_t)h.len, NULL);
1186     bview->buffer->is_unsaved = 0;
1187     mark_move_beginning(bview->active_cursor->mark);
1188     bview_zero_viewport_y(bview);
1189 
1190     str_free(&h);
1191     return MLE_OK;
1192 }
1193 
1194 // Recursively descend into kbinding trie to build help string
_cmd_help_inner(char * buf,kbinding_t * trie,str_t * h)1195 static void _cmd_help_inner(char *buf, kbinding_t *trie, str_t *h) {
1196     kbinding_t *binding;
1197     kbinding_t *binding_tmp;
1198     HASH_ITER(hh, trie, binding, binding_tmp) {
1199         if (binding->children) {
1200             _cmd_help_inner(buf, binding->children, h);
1201         } else if (binding->is_leaf) {
1202             snprintf(buf, 1024,
1203                 "        %-40s %-16s %s\n",
1204                 binding->cmd_name,
1205                 binding->key_patt,
1206                 binding->static_param ? binding->static_param : ""
1207             );
1208             str_append(h, buf);
1209         }
1210     }
1211 }
1212 
1213 // Indent/outdent a line, optionally using tabs
_cmd_indent_line(bline_t * bline,int use_tabs,int outdent)1214 static int _cmd_indent_line(bline_t *bline, int use_tabs, int outdent) {
1215     char tab_char;
1216     int num_chars;
1217     int num_to_del;
1218     int i;
1219     bint_t ig;
1220     tab_char = use_tabs ? '\t' : ' ';
1221     num_chars = use_tabs ? 1 : bline->buffer->tab_width;
1222     MLBUF_BLINE_ENSURE_CHARS(bline);
1223     if (outdent) {
1224         num_to_del = 0;
1225         for (i = 0; i < num_chars; i++) {
1226             if (bline->char_count > i && (char)bline->chars[i].ch == tab_char) {
1227                 num_to_del += 1;
1228             } else {
1229                 break;
1230             }
1231         }
1232         if (num_to_del > 0) bline_delete(bline, 0, num_to_del);
1233     } else {
1234         if (bline->char_count > 0) {
1235             for (i = 0; i < num_chars; i++) {
1236                 bline_insert(bline, 0, &tab_char, 1, &ig);
1237             }
1238         }
1239     }
1240     return MLE_OK;
1241 }
1242 
1243 
1244 // Recursively close bviews, prompting to save unsaved changes. Return MLE_OK if
1245 // it's OK to continue closing, or MLE_ERR if the action was cancelled.
_cmd_quit_inner(editor_t * editor,bview_t * bview)1246 static int _cmd_quit_inner(editor_t *editor, bview_t *bview) {
1247     if (bview->split_child && _cmd_quit_inner(editor, bview->split_child) == MLE_ERR) {
1248         return MLE_ERR;
1249     } else if (_cmd_pre_close(editor, bview) == MLE_ERR) {
1250         return MLE_ERR;
1251     }
1252     editor_close_bview(editor, bview, NULL);
1253     return MLE_OK;
1254 }
1255 
1256 // Prompt to save unsaved changes on close. Return MLE_OK if it's OK to continue
1257 // closing the bview, or MLE_ERR if the action was cancelled.
_cmd_pre_close(editor_t * editor,bview_t * bview)1258 static int _cmd_pre_close(editor_t *editor, bview_t *bview) {
1259     char *yn;
1260 
1261     if (!MLE_BVIEW_IS_EDIT(bview)) {
1262         MLE_RETURN_ERR(editor, "Cannot close non-edit bview %p", (void*)bview);
1263     } else if (editor->loop_depth > 1) {
1264         MLE_RETURN_ERR(editor, "Cannot close bview %p when loop_depth > 1", (void*)bview);
1265     } else if (!bview->buffer->is_unsaved || MLE_BVIEW_IS_MENU(bview)
1266         || editor_count_bviews_by_buffer(editor, bview->buffer) > 1
1267     ) {
1268         return MLE_OK;
1269     }
1270 
1271     editor_set_active(editor, bview);
1272 
1273     yn = NULL;
1274     editor_prompt(editor, "close: Save modified? (y=yes, n=no, C-c=cancel)",
1275         &(editor_prompt_params_t) { .kmap = editor->kmap_prompt_yn }, &yn
1276     );
1277     if (!yn) {
1278         return MLE_ERR;
1279     } else if (0 == strcmp(yn, MLE_PROMPT_NO)) {
1280         return MLE_OK;
1281     }
1282     return _cmd_save(editor, bview, 1);
1283 }
1284 
1285 // Prompt to save changes. Return MLE_OK if file was saved or MLE_ERR if the action
1286 // was cancelled.
_cmd_save(editor_t * editor,bview_t * bview,int save_as)1287 static int _cmd_save(editor_t *editor, bview_t *bview, int save_as) {
1288     int rc;
1289     char *path;
1290     char *yn;
1291     int fname_changed;
1292     struct stat st;
1293     bint_t nbytes;
1294 
1295     fname_changed = 0;
1296     do {
1297         if (!bview->buffer->path || save_as) {
1298             // Prompt for name
1299             editor_prompt(editor, "save: Save as? (C-c=cancel)", &(editor_prompt_params_t) {
1300                 .data = bview->buffer->path ? bview->buffer->path : "",
1301                 .data_len = bview->buffer->path ? strlen(bview->buffer->path) : 0
1302             }, &path);
1303             if (!path) return MLE_ERR;
1304         } else {
1305             // Re-use existing name
1306             path = strdup(bview->buffer->path);
1307         }
1308 
1309         // Remember if fname is changing for refreshing syntax later
1310         fname_changed = !bview->buffer->path || strcmp(bview->buffer->path, path) != 0 ? 1 : 0;
1311 
1312         // Check clobber warning
1313         if (stat(path, &st) == 0
1314             && st.st_dev == bview->buffer->st.st_dev
1315             && st.st_ino == bview->buffer->st.st_ino
1316             && st.st_mtime > bview->buffer->st.st_mtime
1317         ) {
1318             // File was modified after it was opened/last saved
1319             editor_prompt(editor, "save: Clobber detected! Continue? (y=yes, n=no)",
1320                 &(editor_prompt_params_t) { .kmap = editor->kmap_prompt_yn }, &yn
1321             );
1322             if (!yn || 0 == strcmp(yn, MLE_PROMPT_NO)) {
1323                 free(path);
1324                 return MLE_OK;
1325             }
1326         }
1327 
1328         // Save, check error
1329         rc = buffer_save_as(bview->buffer, path, &nbytes);
1330         free(path);
1331         if (rc == MLBUF_ERR) {
1332             MLE_SET_ERR(editor, "save: %s", errno ? strerror(errno) : "failed");
1333         } else {
1334             MLE_SET_INFO(editor, "save: Wrote %" PRIdMAX " bytes", nbytes);
1335             // Notify event observers
1336             editor_notify_observers(editor, "buffer:save", (void*)bview);
1337         }
1338 
1339     } while (rc == MLBUF_ERR && (!bview->buffer->path || save_as));
1340 
1341     // Refresh syntax if fname changed
1342     if (fname_changed) {
1343         bview_set_syntax(bview, NULL);
1344     }
1345     return rc == MLBUF_OK ? MLE_OK : MLE_ERR;
1346 }
1347 
1348 // Move cursor to next occurrence of term, wrap if necessary. Return MLE_OK if
1349 // there was a match, or MLE_ERR if no match.
_cmd_search_next(bview_t * bview,cursor_t * cursor,mark_t * search_mark,char * regex,int regex_len)1350 static int _cmd_search_next(bview_t *bview, cursor_t *cursor, mark_t *search_mark, char *regex, int regex_len) {
1351     int rc;
1352     rc = MLE_ERR;
1353 
1354     // Move search_mark to cursor
1355     mark_join(search_mark, cursor->mark);
1356 
1357     // Look for match ahead of us
1358     if (mark_move_next_re_nudge(search_mark, regex, regex_len) == MLBUF_OK) {
1359         // Match! Move there
1360         mark_join(cursor->mark, search_mark);
1361         rc = MLE_OK;
1362     } else {
1363         // No match, try from beginning
1364         mark_move_beginning(search_mark);
1365         if (mark_move_next_re(search_mark, regex, regex_len) == MLBUF_OK) {
1366             // Match! Move there
1367             mark_join(cursor->mark, search_mark);
1368             rc = MLE_OK;
1369         }
1370     }
1371 
1372     // Rectify viewport if needed
1373     if (rc == MLE_OK) bview_rectify_viewport(bview);
1374 
1375     return rc;
1376 }
1377 
1378 // Aproc callback that writes buf to bview buffer
_cmd_aproc_bview_passthru_cb(aproc_t * aproc,char * buf,size_t buf_len)1379 static void _cmd_aproc_bview_passthru_cb(aproc_t *aproc, char *buf, size_t buf_len) {
1380     mark_t *active_mark;
1381     mark_t *ins_mark;
1382     int is_cursor_at_zero;
1383     bview_t *bview;
1384 
1385     if (!buf || buf_len < 1) return;
1386 
1387     // Remeber if cursor is at 0
1388     bview = (bview_t*)aproc->owner;
1389     active_mark = bview->active_cursor->mark;
1390     is_cursor_at_zero = active_mark->bline->line_index == 0 && active_mark->col == 0 ? 1 : 0;
1391 
1392     // Append data at end of menu buffer
1393     ins_mark = buffer_add_mark(bview->buffer, NULL, 0);
1394     mark_move_end(ins_mark);
1395     mark_insert_before(ins_mark, buf, buf_len);
1396     mark_destroy(ins_mark);
1397     bview_rectify_viewport(bview);
1398 
1399     if (is_cursor_at_zero) mark_move_beginning(active_mark);
1400 }
1401 
1402 // Incremental search prompt callback
_cmd_isearch_prompt_cb(bview_t * bview_prompt,baction_t * action,void * udata)1403 static void _cmd_isearch_prompt_cb(bview_t *bview_prompt, baction_t *action, void *udata) {
1404     bview_t *bview;
1405     char *regex;
1406     int regex_len;
1407     (void)action;
1408     (void)udata;
1409 
1410     bview = bview_prompt->editor->active_edit;
1411 
1412     if (bview->isearch_rule) {
1413         buffer_remove_srule(bview->buffer, bview->isearch_rule);
1414         srule_destroy(bview->isearch_rule);
1415         bview->isearch_rule = NULL;
1416     }
1417 
1418     regex = bview_prompt->buffer->first_line->data;
1419     regex_len = bview_prompt->buffer->first_line->data_len;
1420     if (regex_len < 1) return;
1421 
1422     bview->isearch_rule = srule_new_single(regex, regex_len, 1, 0, TB_YELLOW);
1423     if (!bview->isearch_rule) return;
1424 
1425     buffer_add_srule(bview->buffer, bview->isearch_rule);
1426     mark_move_next_cre(bview->active_cursor->mark, bview->isearch_rule->cre);
1427 
1428     bview_center_viewport_y(bview);
1429 }
1430 
1431 // Callback from cmd_grep
_cmd_menu_grep_cb(cmd_context_t * ctx)1432 static int _cmd_menu_grep_cb(cmd_context_t *ctx) {
1433     bint_t linenum;
1434     char *line;
1435     char *colon;
1436     line = strndup(ctx->bview->active_cursor->mark->bline->data, ctx->bview->active_cursor->mark->bline->data_len);
1437     colon = strchr(line, ':');
1438     if (colon == NULL) {
1439         free(line);
1440         return MLE_OK;
1441     }
1442     if (*(colon + 1) != '\0' && strchr(colon + 1, ':') != NULL) {
1443         linenum = strtoll(colon + 1, NULL, 10);
1444     } else {
1445         linenum = 0;
1446     }
1447     editor_close_bview(ctx->editor, ctx->bview, NULL);
1448     editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, line, (int)(colon - line), 1, linenum, 0, NULL, NULL);
1449     free(line);
1450     return MLE_OK;
1451 }
1452 
1453 // Callback from cmd_ctag
_cmd_menu_ctag_cb(cmd_context_t * ctx)1454 static int _cmd_menu_ctag_cb(cmd_context_t *ctx) {
1455     char *line;
1456     char *tok;
1457     char *fname;
1458     char *re;
1459     char *qre;
1460     char *qre2;
1461     int re_len;
1462     int qre_len;
1463     int i;
1464     bview_t *bview;
1465     line = strndup(ctx->bview->active_cursor->mark->bline->data, ctx->bview->active_cursor->mark->bline->data_len);
1466     i = 0;
1467     fname = NULL;
1468     re = NULL;
1469     tok = strtok(line, "\t");
1470     while (tok) {
1471         if (i == 1) {
1472             fname = tok;
1473         } else if (i == 2) {
1474             re = tok;
1475             break;
1476         }
1477         tok = strtok(NULL, "\t");
1478         i += 1;
1479     }
1480     re_len = re ? strlen(re) : 0;
1481     if (!fname || re_len < 4) {
1482         free(line);
1483         return MLE_OK;
1484     }
1485     re += 2; // Skip leading `/^`
1486     re_len -= 2;
1487     re[re_len-4] = '\0'; // Trunc trailing `$/;"`
1488     re_len -= 4;
1489     util_pcre_replace("([\\.\\\\\\+\\*\\?\\^\\$\\[\\]\\(\\)\\{\\}\\=\\!\\>\\<\\|\\:\\-])", re, "\\\\$1", &qre, &qre_len);
1490     editor_close_bview(ctx->editor, ctx->bview, NULL);
1491     editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, fname, strlen(fname), 1, 0, 0, NULL, &bview);
1492     asprintf(&qre2, "^%s", qre);
1493     mark_move_next_re(bview->active_cursor->mark, qre2, qre_len+1);
1494     bview_center_viewport_y(bview);
1495     free(line);
1496     free(qre);
1497     free(qre2);
1498     return MLE_OK;
1499 }
1500 
1501 // Callback from cmd_browse
_cmd_menu_browse_cb(cmd_context_t * ctx)1502 static int _cmd_menu_browse_cb(cmd_context_t *ctx) {
1503     char *line;
1504     char *path;
1505     char cwd[PATH_MAX];
1506     char *corrected_path;
1507     bview_t *new_bview;
1508 
1509     // Get path from tree output
1510     line = strndup(ctx->bview->active_cursor->mark->bline->data, ctx->bview->active_cursor->mark->bline->data_len);
1511     if ((path = strstr(line, "- ")) != NULL) {
1512         path += 2;
1513     } else if (strcmp(line, "..") == 0) {
1514         path = "..";
1515     } else if (util_is_dir(line)) {
1516         path = line;
1517     } else {
1518         MLE_SET_ERR(ctx->editor, "browse: Cannot browse to: '%s'", line);
1519         free(line);
1520         return MLE_ERR;
1521     }
1522 
1523     // Fix cwd if it changed
1524     getcwd(cwd, PATH_MAX);
1525     if (strcmp(cwd, ctx->bview->init_cwd) != 0) {
1526         asprintf(&corrected_path, "%s/%s", ctx->bview->init_cwd, path);
1527     } else {
1528         corrected_path = strdup(path);
1529     }
1530 
1531     // Open file or browse dir
1532     new_bview = NULL;
1533     if (util_is_dir(corrected_path)) {
1534         chdir(corrected_path);
1535         ctx->bview = ctx->editor->active_edit;
1536         cmd_browse(ctx);
1537     } else {
1538         editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, corrected_path, strlen(corrected_path), 0, 0, 0, NULL, &new_bview);
1539     }
1540 
1541     // Close menu
1542     editor_close_bview(ctx->editor, ctx->bview, NULL);
1543 
1544     // Set new_bview to active
1545     if (new_bview) editor_set_active(ctx->editor, new_bview);
1546 
1547     free(line);
1548     free(corrected_path);
1549 
1550     return MLE_OK;
1551 }
1552 
1553 // Insert newline when auto_indent is enabled (preserves or increases indent)
_cmd_insert_auto_indent_newline(cmd_context_t * ctx)1554 static void _cmd_insert_auto_indent_newline(cmd_context_t *ctx) {
1555     bline_t *prev_bline;
1556     char *prev_line = NULL;
1557     bint_t prev_line_len;
1558     char *indent;
1559     int indent_len;
1560     char *tmp;
1561     mark_insert_before(ctx->cursor->mark, "\n", 1);
1562     prev_bline = ctx->cursor->mark->bline->prev;
1563     prev_line = strndup(prev_bline->data, prev_bline->data_len);
1564     prev_line_len = strlen(prev_line);
1565     if (util_pcre_match("^\\s*", prev_line, prev_line_len, &indent, &indent_len) && indent_len > 0) {
1566         // Insert same whitespace from prev_line on this line
1567         mark_insert_before(ctx->cursor->mark, indent, indent_len);
1568     }
1569     if (prev_line_len > 0 && prev_line[prev_line_len-1] == '{') {
1570         // Insert extra indent if last line ends with '{'
1571         if (ctx->bview->tab_to_space
1572             && indent_len % ctx->bview->tab_width == 0
1573             && util_pcre_match("^ *$", indent, indent_len, NULL, NULL)
1574         ) {
1575             asprintf(&tmp, "%*c", (int)ctx->bview->tab_width, ' ');
1576             mark_insert_before(ctx->cursor->mark, tmp, strlen(tmp));
1577             free(tmp);
1578         } else if (!ctx->bview->tab_to_space
1579             && util_pcre_match("^\\t*$", indent, indent_len, NULL, NULL)
1580         ) {
1581             mark_insert_before(ctx->cursor->mark, "\t", 1);
1582         }
1583     } else if (ctx->loop_ctx->last_cmd
1584         && ctx->loop_ctx->last_cmd->func == cmd_insert_data
1585         && ctx->loop_ctx->last_insert.len == 1
1586         && ctx->loop_ctx->last_insert.data[0] == '\n'
1587         && util_pcre_match("^\\s+$", prev_line, prev_line_len, NULL, NULL)
1588     ) {
1589         // Clear prev line if it's all whitespace and last cmd was also
1590         // inserting a newline
1591         MLBUF_BLINE_ENSURE_CHARS(ctx->cursor->mark->bline->prev);
1592         bline_delete(ctx->cursor->mark->bline->prev, 0, ctx->cursor->mark->bline->prev->char_count);
1593     }
1594     free(prev_line);
1595 }
1596 
1597 // Insert closing curly bracket when auto_indent is enabled (decreases indent)
_cmd_insert_auto_indent_closing_bracket(cmd_context_t * ctx)1598 static void _cmd_insert_auto_indent_closing_bracket(cmd_context_t *ctx) {
1599     char *this_line = NULL;
1600     char *this_ws;
1601     char *prev_line = NULL;
1602     char *prev_ws;
1603     int this_line_len;
1604     int this_ws_len;
1605     int prev_line_len;
1606     int prev_ws_len;
1607     int prev_open;
1608     do {
1609         if (!ctx->cursor->mark->bline->prev) break;
1610         this_line = strndup(ctx->cursor->mark->bline->data, ctx->cursor->mark->bline->data_len);
1611         prev_line = strndup(ctx->cursor->mark->bline->prev->data, ctx->cursor->mark->bline->prev->data_len);
1612         this_line_len = strlen(this_line);
1613         prev_line_len = strlen(prev_line);
1614         prev_open = prev_line_len > 0 && prev_line[prev_line_len-1] == '{' ? 1 : 0;
1615         if (!util_pcre_match("^\\s*", this_line, this_line_len, &this_ws, &this_ws_len)
1616             || !util_pcre_match("^\\s*", prev_line, prev_line_len, &prev_ws, &prev_ws_len)
1617             || (!prev_open && this_ws_len < prev_ws_len)
1618             || (prev_open && this_ws_len <= prev_ws_len)
1619         ) {
1620             // More whitespace on prev line
1621             break;
1622         } else if (ctx->bview->tab_to_space
1623             && ctx->cursor->mark->bline->data_len % ctx->bview->tab_width == 0
1624             && util_pcre_match("^ +$", this_line, this_line_len, NULL, NULL)
1625         ) {
1626             // Outdent with tab_width worth of spaces
1627             bline_delete(ctx->cursor->mark->bline, 0, ctx->bview->tab_width);
1628         } else if (!ctx->bview->tab_to_space
1629             && util_pcre_match("^\\t+$", this_line, this_line_len, NULL, NULL)
1630         ) {
1631             // Outdent one tab
1632             bline_delete(ctx->cursor->mark->bline, 0, 1);
1633         }
1634     } while(0);
1635     mark_insert_before(ctx->cursor->mark, "}", 1);
1636     if (this_line) free(this_line);
1637     if (prev_line) free(prev_line);
1638 }
1639 
1640 // Apply cmd for each cursor
_cmd_shell_apply_cmd(cmd_context_t * ctx,char * cmd)1641 static void _cmd_shell_apply_cmd(cmd_context_t *ctx, char *cmd) {
1642     char *input;
1643     bint_t input_len;
1644     char *output;
1645     size_t output_len;
1646 
1647     // Loop for each cursor
1648     MLE_MULTI_CURSOR_CODE(ctx->cursor,
1649         // Get data to send to stdin
1650         if (ctx->cursor->is_anchored) {
1651             mark_get_between_mark(cursor->mark, cursor->anchor, &input, &input_len);
1652         } else {
1653             input = NULL;
1654             input_len = 0;
1655         }
1656 
1657         // Run cmd
1658         output = NULL;
1659         output_len = 0;
1660         if (util_shell_exec(ctx->editor, cmd, 1, input, input_len, 0, NULL, &output, &output_len) == MLE_OK && output_len > 0) {
1661             // Write output to buffer
1662             if (cursor->is_anchored) {
1663                 mark_delete_between_mark(cursor->mark, cursor->anchor);
1664             }
1665             mark_insert_before(cursor->mark, output, output_len);
1666         }
1667 
1668         // Free input and output
1669         if (input) free(input);
1670         if (output) free(output);
1671     ); // Loop for next cursor
1672 }
1673 
1674 // Get one kinput_t, saving original input on cmd_context_t
_cmd_get_input(cmd_context_t * ctx,kinput_t * ret_input)1675 static void _cmd_get_input(cmd_context_t *ctx, kinput_t *ret_input) {
1676     kinput_t oinput;
1677     MLE_KINPUT_COPY(oinput, ctx->input);
1678     memset(ret_input, 0, sizeof(kinput_t));
1679     editor_get_input(ctx->editor, ctx->loop_ctx, ctx);
1680     MLE_KINPUT_COPY(*ret_input, ctx->input);
1681     MLE_KINPUT_COPY(ctx->input, oinput);
1682 }
1683 
1684 // Perform a fuzzy search using fzf/fzy
1685 // `shell_cmd` is expected to return a path on stdout
_cmd_fsearch_inner(cmd_context_t * ctx,char * shell_cmd)1686 static int _cmd_fsearch_inner(cmd_context_t *ctx, char *shell_cmd) {
1687     char *path;
1688     size_t path_len;
1689     path = NULL;
1690     if (tb_width() >= 0) tb_shutdown();
1691     if (util_shell_exec(ctx->editor, shell_cmd, -1, NULL, 0, 0, NULL, &path, &path_len) == MLE_ERR) {
1692         return MLE_ERR;
1693     }
1694     _cmd_force_redraw(ctx);
1695     if (path && path_len > 0) {
1696         while (path[path_len-1] == '\n') {
1697             // Strip trailing newlines
1698             path_len -= 1;
1699         }
1700         if (path_len > 0) {
1701             if (ctx->static_param && strcmp(ctx->static_param, "replace") == 0) {
1702                 if (_cmd_pre_close(ctx->editor, ctx->bview) == MLE_OK) {
1703                     bview_open(ctx->bview, path, path_len);
1704                 }
1705             } else {
1706                 editor_open_bview(ctx->editor, NULL, MLE_BVIEW_TYPE_EDIT, path, path_len, 1, 0, 0, NULL, NULL);
1707             }
1708         }
1709     }
1710     free(path);
1711     return MLE_OK;
1712 }
1713