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, ®ex);
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