1 #include <unistd.h>
2 #include <signal.h>
3 #include <termbox.h>
4 #include <uthash.h>
5 #include <utlist.h>
6 #include <inttypes.h>
7 #include "mle.h"
8 #include "mlbuf.h"
9 
10 static int _editor_set_macro_toggle_key(editor_t *editor, char *key);
11 static int _editor_bview_exists(editor_t *editor, bview_t *bview);
12 static int _editor_register_cmd_fn(editor_t *editor, char *name, int (*func)(cmd_context_t *ctx));
13 static int _editor_should_skip_rc(char **argv);
14 static int _editor_close_bview_inner(editor_t *editor, bview_t *bview, int *optret_num_closed);
15 static int _editor_destroy_cmd(editor_t *editor, cmd_t *cmd);
16 static int _editor_prompt_input_submit(cmd_context_t *ctx);
17 static int _editor_prompt_input_complete(cmd_context_t *ctx);
18 static prompt_history_t *_editor_prompt_find_or_add_history(cmd_context_t *ctx, prompt_hnode_t **optret_prompt_hnode);
19 static int _editor_prompt_history_up(cmd_context_t *ctx);
20 static int _editor_prompt_history_down(cmd_context_t *ctx);
21 static int _editor_prompt_history_append(cmd_context_t *ctx, char *data);
22 static int _editor_prompt_yna_all(cmd_context_t *ctx);
23 static int _editor_prompt_yn_yes(cmd_context_t *ctx);
24 static int _editor_prompt_yn_no(cmd_context_t *ctx);
25 static int _editor_prompt_cancel(cmd_context_t *ctx);
26 static int _editor_menu_submit(cmd_context_t *ctx);
27 static int _editor_menu_cancel(cmd_context_t *ctx);
28 static int _editor_prompt_menu_up(cmd_context_t *ctx);
29 static int _editor_prompt_menu_down(cmd_context_t *ctx);
30 static int _editor_prompt_menu_page_up(cmd_context_t *ctx);
31 static int _editor_prompt_menu_page_down(cmd_context_t *ctx);
32 static int _editor_prompt_isearch_drop_cursors(cmd_context_t *ctx);
33 static int _editor_prompt_isearch_next(cmd_context_t *ctx);
34 static int _editor_prompt_isearch_prev(cmd_context_t *ctx);
35 static int _editor_prompt_isearch_viewport_down(cmd_context_t *ctx);
36 static int _editor_prompt_isearch_viewport_up(cmd_context_t *ctx);
37 static void _editor_loop(editor_t *editor, loop_context_t *loop_ctx);
38 static void _editor_refresh_cmd_context(editor_t *editor, cmd_context_t *ctx);
39 static void _editor_notify_cmd_observers(cmd_context_t *ctx, int is_before);
40 static int _editor_maybe_toggle_macro(editor_t *editor, kinput_t *input);
41 static void _editor_resize(editor_t *editor, int w, int h);
42 static void _editor_draw_cursors(editor_t *editor, bview_t *bview);
43 static void _editor_get_user_input(editor_t *editor, cmd_context_t *ctx);
44 static void _editor_ingest_paste(editor_t *editor, cmd_context_t *ctx);
45 static void _editor_record_macro_input(kmacro_t *macro, kinput_t *input);
46 static cmd_t *_editor_get_command(editor_t *editor, cmd_context_t *ctx, kinput_t *opt_peek_input);
47 static kbinding_t *_editor_get_kbinding_node(kbinding_t *node, kinput_t *input, loop_context_t *loop_ctx, int is_peek, int *ret_again);
48 static cmd_t *_editor_resolve_cmd(editor_t *editor, cmd_t **rcmd, char *cmd_name);
49 static int _editor_key_to_input(char *key, kinput_t *ret_input);
50 static void _editor_init_signal_handlers(editor_t *editor);
51 static void _editor_graceful_exit(int signum);
52 static void _editor_register_cmds(editor_t *editor);
53 static void _editor_init_kmaps(editor_t *editor);
54 static void _editor_init_kmap(editor_t *editor, kmap_t **ret_kmap, char *name, char *default_cmd_name, int allow_fallthru, kbinding_def_t *defs);
55 static void _editor_init_kmap_add_binding(editor_t *editor, kmap_t *kmap, kbinding_def_t *binding_def);
56 static int _editor_init_kmap_add_binding_to_trie(kbinding_t **trie, char *cmd_name, char *cur_key_patt, char *full_key_patt, char *static_param);
57 static int _editor_init_kmap_by_str(editor_t *editor, kmap_t **ret_kmap, char *str);
58 static int _editor_init_kmap_add_binding_by_str(editor_t *editor, kmap_t *kmap, char *str);
59 static void _editor_destroy_kmap(kmap_t *kmap, kbinding_t *trie);
60 static int _editor_add_macro_by_str(editor_t *editor, char *str);
61 static void _editor_init_syntaxes(editor_t *editor);
62 static void _editor_init_syntax(editor_t *editor, syntax_t **optret_syntax, char *name, char *path_pattern, int tab_width, int tab_to_space, srule_def_t *defs);
63 static int _editor_init_syntax_by_str(editor_t *editor, syntax_t **ret_syntax, char *str);
64 static void _editor_init_syntax_add_rule(syntax_t *syntax, srule_def_t *def);
65 static int _editor_init_syntax_add_rule_by_str(syntax_t *syntax, char *str);
66 static void _editor_destroy_syntax_map(syntax_t *map);
67 static int _editor_init_from_rc_read(editor_t *editor, FILE *rc, char **ret_rc_data, size_t *ret_rc_data_len);
68 static int _editor_init_from_rc_exec(editor_t *editor, char *rc_path, char **ret_rc_data, size_t *ret_rc_data_len);
69 static int _editor_init_from_rc(editor_t *editor, FILE *rc, char *rc_path);
70 static int _editor_init_from_args(editor_t *editor, int argc, char **argv);
71 static void _editor_init_status(editor_t *editor);
72 static void _editor_init_bviews(editor_t *editor, int argc, char **argv);
73 static int _editor_init_headless_mode(editor_t *editor);
74 static int _editor_init_startup_macro(editor_t *editor);
75 
76 // Init editor from args
editor_init(editor_t * editor,int argc,char ** argv)77 int editor_init(editor_t *editor, int argc, char **argv) {
78     int rv;
79     FILE *rc;
80     char *home_rc;
81     rv = MLE_OK;
82     do {
83         // Set editor defaults
84         editor->is_in_init = 1;
85         editor->tab_width = MLE_DEFAULT_TAB_WIDTH;
86         editor->tab_to_space = MLE_DEFAULT_TAB_TO_SPACE;
87         editor->trim_paste = MLE_DEFAULT_TRIM_PASTE;
88         editor->auto_indent = MLE_DEFAULT_AUTO_INDENT;
89         editor->highlight_bracket_pairs = MLE_DEFAULT_HILI_BRACKET_PAIRS;
90         editor->read_rc_file = MLE_DEFAULT_READ_RC_FILE;
91         editor->soft_wrap = MLE_DEFAULT_SOFT_WRAP;
92         editor->viewport_scope_x = -4;
93         editor->viewport_scope_y = -1;
94         editor->color_col = -1;
95         editor->exit_code = EXIT_SUCCESS;
96         editor->headless_mode = isatty(STDIN_FILENO) == 0 ? 1 : 0;
97         _editor_set_macro_toggle_key(editor, MLE_DEFAULT_MACRO_TOGGLE_KEY);
98 
99         // Init signal handlers
100         _editor_init_signal_handlers(editor);
101 
102         // Register commands
103         _editor_register_cmds(editor);
104 
105         // Init kmaps
106         _editor_init_kmaps(editor);
107 
108         // Init syntaxes
109         _editor_init_syntaxes(editor);
110 
111         // Parse rc files
112         if (!_editor_should_skip_rc(argv)) {
113             home_rc = NULL;
114             if (getenv("HOME")) {
115                 asprintf(&home_rc, "%s/%s", getenv("HOME"), ".mlerc");
116                 if (util_is_file(home_rc, "rb", &rc)) {
117                     rv = _editor_init_from_rc(editor, rc, home_rc);
118                     fclose(rc);
119                 }
120                 free(home_rc);
121             }
122             if (rv != MLE_OK) break;
123             if (util_is_file("/etc/mlerc", "rb", &rc)) {
124                 rv = _editor_init_from_rc(editor, rc, "/etc/mlerc");
125                 fclose(rc);
126             }
127             if (rv != MLE_OK) break;
128         }
129 
130         // Parse cli args
131         rv = _editor_init_from_args(editor, argc, argv);
132         if (rv != MLE_OK) break;
133 
134         // Init status bar
135         _editor_init_status(editor);
136 
137         // Init bviews
138         _editor_init_bviews(editor, argc, argv);
139 
140         // Init startup macro
141         _editor_init_headless_mode(editor);
142 
143         // Init headless mode
144         _editor_init_startup_macro(editor);
145     } while(0);
146 
147     editor->is_in_init = 0;
148     return rv;
149 }
150 
151 // Run editor
editor_run(editor_t * editor)152 int editor_run(editor_t *editor) {
153     loop_context_t loop_ctx;
154     memset(&loop_ctx, 0, sizeof(loop_context_t));
155     _editor_resize(editor, -1, -1);
156     _editor_loop(editor, &loop_ctx);
157     return MLE_OK;
158 }
159 
160 // Deinit editor
editor_deinit(editor_t * editor)161 int editor_deinit(editor_t *editor) {
162     bview_t *bview;
163     bview_t *bview_tmp1;
164     bview_t *bview_tmp2;
165     kmap_t *kmap;
166     kmap_t *kmap_tmp;
167     kmacro_t *macro;
168     kmacro_t *macro_tmp;
169     cmd_t *cmd;
170     cmd_t *cmd_tmp;
171     prompt_history_t *prompt_history;
172     prompt_history_t *prompt_history_tmp;
173     prompt_hnode_t *prompt_hnode;
174     prompt_hnode_t *prompt_hnode_tmp1;
175     prompt_hnode_t *prompt_hnode_tmp2;
176     observer_t *observer;
177     observer_t *observer_tmp;
178     if (editor->status) bview_destroy(editor->status);
179     CDL_FOREACH_SAFE2(editor->all_bviews, bview, bview_tmp1, bview_tmp2, all_prev, all_next) {
180         CDL_DELETE2(editor->all_bviews, bview, all_prev, all_next);
181         bview_destroy(bview);
182     }
183     HASH_ITER(hh, editor->kmap_map, kmap, kmap_tmp) {
184         HASH_DEL(editor->kmap_map, kmap);
185         _editor_destroy_kmap(kmap, kmap->bindings->children);
186         if (kmap->default_cmd_name) free(kmap->default_cmd_name);
187         free(kmap->bindings);
188         free(kmap->name);
189         free(kmap);
190     }
191     HASH_ITER(hh, editor->macro_map, macro, macro_tmp) {
192         HASH_DEL(editor->macro_map, macro);
193         if (macro->inputs) free(macro->inputs);
194         if (macro->name) free(macro->name);
195         free(macro);
196     }
197     HASH_ITER(hh, editor->cmd_map, cmd, cmd_tmp) {
198         HASH_DEL(editor->cmd_map, cmd);
199         _editor_destroy_cmd(editor, cmd);
200     }
201     HASH_ITER(hh, editor->prompt_history, prompt_history, prompt_history_tmp) {
202         HASH_DEL(editor->prompt_history, prompt_history);
203         free(prompt_history->prompt_str);
204         CDL_FOREACH_SAFE(prompt_history->prompt_hlist, prompt_hnode, prompt_hnode_tmp1, prompt_hnode_tmp2) {
205             CDL_DELETE(prompt_history->prompt_hlist, prompt_hnode);
206             free(prompt_hnode->data);
207             free(prompt_hnode);
208         }
209         free(prompt_history);
210     }
211     DL_FOREACH_SAFE(editor->observers, observer, observer_tmp) {
212         editor_destroy_observer(editor, observer);
213     }
214     if (editor->macro_record) {
215         if (editor->macro_record->inputs) free(editor->macro_record->inputs);
216         free(editor->macro_record);
217     }
218     _editor_destroy_syntax_map(editor->syntax_map);
219     if (editor->kmap_init_name) free(editor->kmap_init_name);
220     if (editor->insertbuf) free(editor->insertbuf);
221     if (editor->cut_buffer) free(editor->cut_buffer);
222     if (editor->ttyfd) close(editor->ttyfd);
223     if (editor->startup_macro_name) free(editor->startup_macro_name);
224     return MLE_OK;
225 }
226 
227 // Prompt user for input
editor_prompt(editor_t * editor,char * prompt,editor_prompt_params_t * params,char ** optret_answer)228 int editor_prompt(editor_t *editor, char *prompt, editor_prompt_params_t *params, char **optret_answer) {
229     bview_t *bview_tmp;
230     loop_context_t loop_ctx;
231     memset(&loop_ctx, 0, sizeof(loop_context_t));
232 
233     // Disallow nested prompts
234     if (editor->prompt) {
235         if (optret_answer) *optret_answer = NULL;
236         return MLE_ERR;
237     }
238 
239     // Init loop_ctx
240     loop_ctx.invoker = editor->active;
241     loop_ctx.should_exit = 0;
242     loop_ctx.prompt_answer = NULL;
243 
244     // Init prompt
245     editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_PROMPT, NULL, 0, 1, 0, 0, NULL, &editor->prompt);
246     if (params && params->prompt_cb) bview_add_listener(editor->prompt, params->prompt_cb, params->prompt_cb_udata);
247     editor->prompt->prompt_str = prompt;
248     bview_push_kmap(editor->prompt, params && params->kmap ? params->kmap : editor->kmap_prompt_input);
249 
250     // Insert data if present
251     if (params && params->data && params->data_len > 0) {
252         buffer_insert(editor->prompt->buffer, 0, params->data, params->data_len, NULL);
253         mark_move_eol(editor->prompt->active_cursor->mark);
254     }
255 
256     // Loop inside prompt
257     _editor_loop(editor, &loop_ctx);
258 
259     // Set answer
260     if (optret_answer) {
261         *optret_answer = loop_ctx.prompt_answer;
262     } else if (loop_ctx.prompt_answer) {
263         free(loop_ctx.prompt_answer);
264         loop_ctx.prompt_answer = NULL;
265     }
266 
267     // Restore previous focus
268     bview_tmp = editor->prompt;
269     editor->prompt = NULL;
270     editor_close_bview(editor, bview_tmp, NULL);
271     editor_set_active(editor, loop_ctx.invoker);
272 
273     return MLE_OK;
274 }
275 
276 // Open dialog menu
editor_menu(editor_t * editor,cmd_func_t callback,char * opt_buf_data,int opt_buf_data_len,aproc_t * opt_aproc,bview_t ** optret_menu)277 int editor_menu(editor_t *editor, cmd_func_t callback, char *opt_buf_data, int opt_buf_data_len, aproc_t *opt_aproc, bview_t **optret_menu) {
278     bview_t *menu;
279     editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, &menu);
280     menu->is_menu = 1;
281     menu->menu_callback = callback;
282     bview_push_kmap(menu, editor->kmap_menu);
283     if (opt_aproc) {
284         aproc_set_owner(opt_aproc, menu, &(menu->aproc));
285     }
286     if (opt_buf_data) {
287         mark_insert_before(menu->active_cursor->mark, opt_buf_data, opt_buf_data_len);
288     }
289     if (optret_menu) *optret_menu = menu;
290     return MLE_OK;
291 }
292 
293 // Open a bview
editor_open_bview(editor_t * editor,bview_t * opt_parent,int type,char * opt_path,int opt_path_len,int make_active,bint_t linenum,int skip_resize,buffer_t * opt_buffer,bview_t ** optret_bview)294 int editor_open_bview(editor_t *editor, bview_t *opt_parent, int type, char *opt_path, int opt_path_len, int make_active, bint_t linenum, int skip_resize, buffer_t *opt_buffer, bview_t **optret_bview) {
295     bview_t *bview;
296     bview_rect_t *rect;
297     int found;
298     found = 0;
299     // Check if already open and not dirty
300     if (opt_path) {
301         CDL_FOREACH2(editor->all_bviews, bview, all_next) {
302             if (bview->buffer
303                 && !bview->buffer->is_unsaved
304                 && bview->buffer->path
305                 && strcmp(opt_path, bview->buffer->path) == 0
306             ) {
307                 found = 1;
308                 break;
309             }
310         }
311     }
312     // Make new bview if not already open
313     if (!found) {
314         bview = bview_new(editor, opt_path, opt_path_len, opt_buffer);
315         bview->type = type;
316         CDL_APPEND2(editor->all_bviews, bview, all_prev, all_next);
317         if (!opt_parent) {
318             DL_APPEND2(editor->top_bviews, bview, top_prev, top_next);
319         } else {
320             opt_parent->split_child = bview;
321         }
322     }
323     if (make_active) {
324         editor_set_active(editor, bview);
325     }
326     if (!found && !editor->is_in_init && !skip_resize) {
327         switch (type) {
328             case MLE_BVIEW_TYPE_STATUS: rect = &editor->rect_status; break;
329             case MLE_BVIEW_TYPE_PROMPT: rect = &editor->rect_prompt; break;
330             default:
331             case MLE_BVIEW_TYPE_EDIT:   rect = &editor->rect_edit;   break;
332         }
333         bview_resize(bview, rect->x, rect->y, rect->w, rect->h);
334     }
335     if (linenum > 0) {
336         mark_move_to(bview->active_cursor->mark, linenum - 1, 0);
337         bview_center_viewport_y(bview);
338     }
339     if (optret_bview) {
340         *optret_bview = bview;
341     }
342     if (!found && opt_path && util_is_dir(opt_path)) {
343         // TODO This is hacky
344         cmd_context_t ctx;
345         memset(&ctx, 0, sizeof(cmd_context_t));
346         ctx.editor = editor;
347         ctx.static_param = strndup(opt_path, opt_path_len);
348         ctx.bview = bview;
349         cmd_browse(&ctx);
350         editor_close_bview(editor, bview, NULL);
351         free(ctx.static_param);
352         ctx.static_param = NULL;
353     }
354     return MLE_OK;
355 }
356 
357 // Close a bview
editor_close_bview(editor_t * editor,bview_t * bview,int * optret_num_closed)358 int editor_close_bview(editor_t *editor, bview_t *bview, int *optret_num_closed) {
359     int rc;
360     if (optret_num_closed) *optret_num_closed = 0;
361     if ((rc = _editor_close_bview_inner(editor, bview, optret_num_closed)) == MLE_OK) {
362         _editor_resize(editor, editor->w, editor->h);
363     }
364     return rc;
365 }
366 
367 // Set the active bview
editor_set_active(editor_t * editor,bview_t * bview)368 int editor_set_active(editor_t *editor, bview_t *bview) {
369     if (!_editor_bview_exists(editor, bview)) {
370         MLE_RETURN_ERR(editor, "No bview %p in editor->all_bviews", (void*)bview);
371     } else if (editor->prompt) {
372         MLE_RETURN_ERR(editor, "Cannot abandon prompt for bview %p", (void*)bview);
373     }
374     editor->active = bview;
375     if (MLE_BVIEW_IS_EDIT(bview)) {
376         editor->active_edit = bview;
377         editor->active_edit_root = bview_get_split_root(bview);
378     }
379     bview_rectify_viewport(bview);
380     return MLE_OK;
381 }
382 
383 // Print debug info
editor_debug_dump(editor_t * editor,FILE * fp)384 int editor_debug_dump(editor_t *editor, FILE *fp) {
385     bview_t *bview;
386     cursor_t *cursor;
387     buffer_t *buffer;
388     int bview_index;
389     int cursor_index;
390     bview_index = 0;
391     CDL_FOREACH2(editor->all_bviews, bview, all_next) {
392         if (!MLE_BVIEW_IS_EDIT(bview)) continue;
393         cursor_index = 0;
394         DL_FOREACH(bview->cursors, cursor) {
395             fprintf(fp, "bview.%d.cursor.%d.mark.line_index=%" PRIdMAX "\n", bview_index, cursor_index, cursor->mark->bline->line_index);
396             fprintf(fp, "bview.%d.cursor.%d.mark.col=%" PRIdMAX "\n", bview_index, cursor_index, cursor->mark->col);
397             if (cursor->is_anchored) {
398                 fprintf(fp, "bview.%d.cursor.%d.anchor.line_index=%" PRIdMAX "\n", bview_index, cursor_index, cursor->anchor->bline->line_index);
399                 fprintf(fp, "bview.%d.cursor.%d.anchor.col=%" PRIdMAX "\n", bview_index, cursor_index, cursor->anchor->col);
400             }
401             cursor_index += 1;
402         }
403         fprintf(fp, "bview.%d.cursor_count=%d\n", bview_index, cursor_index);
404         buffer = bview->buffer;
405         fprintf(fp, "bview.%d.buffer.byte_count=%" PRIdMAX "\n", bview_index, buffer->byte_count);
406         fprintf(fp, "bview.%d.buffer.line_count=%" PRIdMAX "\n", bview_index, buffer->line_count);
407         if (buffer->path) {
408             fprintf(fp, "bview.%d.buffer.path=%s\n", bview_index, buffer->path);
409         }
410         fprintf(fp, "bview.%d.buffer.data_begin\n", bview_index);
411         buffer_write_to_file(buffer, fp, NULL);
412         fprintf(fp, "\nbview.%d.buffer.data_end\n", bview_index);
413         bview_index += 1;
414     }
415     fprintf(fp, "bview_count=%d\n", bview_index);
416     return MLE_OK;
417 }
418 
419 // Given a kinput, put the key name in keybuf
editor_input_to_key(editor_t * editor,kinput_t * input,char * keybuf)420 int editor_input_to_key(editor_t *editor, kinput_t *input, char *keybuf) {
421     int nbytes;
422     (void)editor;
423     #define MLE_KEY_DEF(pckey, pmod, pch, pkey) \
424         } else if (input->key == (pkey) && input->ch == (pch) && input->mod == (pmod)) { \
425             sprintf(keybuf, "%s", (pckey)); \
426             return MLE_OK;
427     if (0) {
428         #include "keys.h"
429     }
430     #undef MLE_KEY_DEF
431     if (input->mod == TB_MOD_ALT) {
432         sprintf(keybuf, "M-");
433         keybuf += 2;
434     }
435     nbytes = utf8_unicode_to_char(keybuf, input->ch);
436     keybuf[nbytes] = '\0';
437     return MLE_ERR;
438 }
439 
440 // Set macro toggle key
_editor_set_macro_toggle_key(editor_t * editor,char * key)441 static int _editor_set_macro_toggle_key(editor_t *editor, char *key) {
442     return _editor_key_to_input(key, &editor->macro_toggle_key);
443 }
444 
445 // Return 1 if bview exists in editor, else return 0
_editor_bview_exists(editor_t * editor,bview_t * bview)446 static int _editor_bview_exists(editor_t *editor, bview_t *bview) {
447     bview_t *tmp;
448     CDL_FOREACH2(editor->all_bviews, tmp, all_next) {
449         if (tmp == bview) return 1;
450     }
451     return 0;
452 }
453 
454 // Return number of EDIT bviews open
editor_bview_edit_count(editor_t * editor)455 int editor_bview_edit_count(editor_t *editor) {
456     int count;
457     bview_t *bview;
458     count = 0;
459     CDL_FOREACH2(editor->all_bviews, bview, all_next) {
460         if (MLE_BVIEW_IS_EDIT(bview)) count += 1;
461     }
462     return count;
463 }
464 
465 // Return number of bviews displaying buffer
editor_count_bviews_by_buffer(editor_t * editor,buffer_t * buffer)466 int editor_count_bviews_by_buffer(editor_t *editor, buffer_t *buffer) {
467     int count;
468     bview_t *bview;
469     count = 0;
470     CDL_FOREACH2(editor->all_bviews, bview, all_next) {
471         if (bview->buffer == buffer) count += 1;
472     }
473     return count;
474 }
475 
476 // Register a command
_editor_register_cmd_fn(editor_t * editor,char * name,int (* func)(cmd_context_t * ctx))477 static int _editor_register_cmd_fn(editor_t *editor, char *name, int (*func)(cmd_context_t *ctx)) {
478     cmd_t cmd = {0};
479     cmd.name = name;
480     cmd.func = func;
481     return editor_register_cmd(editor, &cmd);
482 }
483 
484 // Register a command (extended)
editor_register_cmd(editor_t * editor,cmd_t * cmd)485 int editor_register_cmd(editor_t *editor, cmd_t *cmd) {
486     cmd_t *existing_cmd;
487     cmd_t *new_cmd;
488     HASH_FIND_STR(editor->cmd_map, cmd->name, existing_cmd);
489     if (existing_cmd) return MLE_ERR;
490     new_cmd = calloc(1, sizeof(cmd_t));
491     *new_cmd = *cmd;
492     new_cmd->name = strdup(new_cmd->name);
493     HASH_ADD_KEYPTR(hh, editor->cmd_map, new_cmd->name, strlen(new_cmd->name), new_cmd);
494     return MLE_OK;
495 }
496 
497 // Get input from either macro or user
editor_get_input(editor_t * editor,loop_context_t * loop_ctx,cmd_context_t * ctx)498 int editor_get_input(editor_t *editor, loop_context_t *loop_ctx, cmd_context_t *ctx) {
499     ctx->is_user_input = 0;
500     if (editor->macro_apply
501         && editor->macro_apply_input_index < editor->macro_apply->inputs_len
502     ) {
503         // Get input from macro
504         MLE_KINPUT_COPY(ctx->input, editor->macro_apply->inputs[editor->macro_apply_input_index]);
505         editor->macro_apply_input_index += 1;
506     } else {
507         // Get input from user
508         if (editor->macro_apply) {
509             // Clear macro if present
510             editor->macro_apply = NULL;
511             editor->macro_apply_input_index = 0;
512         }
513         if (editor->headless_mode) {
514             // Bail if in headless mode
515             loop_ctx->should_exit = 1;
516             return MLE_ERR;
517         } else {
518             // Get input from user
519             _editor_get_user_input(editor, ctx);
520             ctx->is_user_input = 1;
521         }
522     }
523     if (editor->is_recording_macro && editor->macro_record) {
524         // Record macro input
525         _editor_record_macro_input(editor->macro_record, &ctx->input);
526     }
527     return MLE_OK;
528 }
529 
530 // Display the editor
editor_display(editor_t * editor)531 int editor_display(editor_t *editor) {
532     bview_t *bview;
533     if (editor->headless_mode) return MLE_OK;
534     tb_clear();
535     bview_draw(editor->active_edit_root);
536     bview_draw(editor->status);
537     if (editor->prompt) bview_draw(editor->prompt);
538     DL_FOREACH2(editor->top_bviews, bview, top_next) {
539         _editor_draw_cursors(editor, bview);
540     }
541     tb_present();
542     return MLE_OK;
543 }
544 
545 // Register a cmd observer
editor_register_observer(editor_t * editor,char * event_name,void * udata,observer_func_t fn_callback,observer_t ** optret_observer)546 int editor_register_observer(editor_t *editor, char *event_name, void *udata, observer_func_t fn_callback, observer_t **optret_observer) {
547     observer_t *observer;
548     observer = calloc(1, sizeof(observer_t));
549     observer->event_name = strdup(event_name);
550     observer->callback = fn_callback;
551     observer->udata = udata;
552     DL_APPEND(editor->observers, observer);
553     if (optret_observer) *optret_observer = observer;
554     return MLE_OK;
555 }
556 
557 // Register a cmd observer
editor_destroy_observer(editor_t * editor,observer_t * observer)558 int editor_destroy_observer(editor_t *editor, observer_t *observer) {
559     DL_DELETE(editor->observers, observer);
560     free(observer->event_name);
561     free(observer);
562     return MLE_OK;
563 }
564 
565 // Return 1 if we should skip reading rc files
_editor_should_skip_rc(char ** argv)566 static int _editor_should_skip_rc(char **argv) {
567     int skip = 0;
568     while (*argv) {
569         if (strcmp("-h", *argv) == 0 || strcmp("-N", *argv) == 0) {
570             skip = 1;
571             break;
572         }
573         argv++;
574     }
575     return skip;
576 }
577 
578 // Close a bview
_editor_close_bview_inner(editor_t * editor,bview_t * bview,int * optret_num_closed)579 static int _editor_close_bview_inner(editor_t *editor, bview_t *bview, int *optret_num_closed) {
580     if (!_editor_bview_exists(editor, bview)) {
581         MLE_RETURN_ERR(editor, "No bview %p in editor->all_bviews", (void*)bview);
582     }
583     if (bview->split_child) {
584         _editor_close_bview_inner(editor, bview->split_child, optret_num_closed);
585     }
586     if (bview->split_parent) {
587         bview->split_parent->split_child = NULL;
588         editor_set_active(editor, bview->split_parent);
589     } else {
590         if (bview->all_prev && bview->all_prev != bview && MLE_BVIEW_IS_EDIT(bview->all_prev)) {
591             editor_set_active(editor, bview->all_prev);
592         } else if (bview->all_next && bview->all_next != bview && MLE_BVIEW_IS_EDIT(bview->all_next)) {
593             editor_set_active(editor, bview->all_next);
594         } else {
595             editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, NULL);
596         }
597     }
598     if (!bview->split_parent) {
599         DL_DELETE2(editor->top_bviews, bview, top_prev, top_next);
600     }
601     CDL_DELETE2(editor->all_bviews, bview, all_prev, all_next);
602     bview_destroy(bview);
603     if (optret_num_closed) *optret_num_closed += 1;
604     return MLE_OK;
605 }
606 
607 // Destroy a command
_editor_destroy_cmd(editor_t * editor,cmd_t * cmd)608 static int _editor_destroy_cmd(editor_t *editor, cmd_t *cmd) {
609     (void)editor;
610     free(cmd->name);
611     free(cmd);
612     return MLE_OK;
613 }
614 
615 // Invoked when user hits enter in a prompt_input
_editor_prompt_input_submit(cmd_context_t * ctx)616 static int _editor_prompt_input_submit(cmd_context_t *ctx) {
617     bint_t answer_len;
618     char *answer;
619     buffer_get(ctx->bview->buffer, &answer, &answer_len);
620     ctx->loop_ctx->prompt_answer = strndup(answer, answer_len);
621     _editor_prompt_history_append(ctx, ctx->loop_ctx->prompt_answer);
622     ctx->loop_ctx->should_exit = 1;
623     return MLE_OK;
624 }
625 
626 // Invoke when user hits tab in a prompt_input
_editor_prompt_input_complete(cmd_context_t * ctx)627 static int _editor_prompt_input_complete(cmd_context_t *ctx) {
628     loop_context_t *loop_ctx;
629     loop_ctx = ctx->loop_ctx;
630     char *cmd;
631     char *cmd_arg;
632     char *terms;
633     size_t terms_len;
634     int num_terms;
635     char *term;
636     int term_index;
637 
638     // Update tab_complete_term and tab_complete_index
639     if (loop_ctx->last_cmd && loop_ctx->last_cmd->func == _editor_prompt_input_complete) {
640         loop_ctx->tab_complete_index += 1;
641     } else if (ctx->bview->buffer->first_line->data_len < MLE_LOOP_CTX_MAX_COMPLETE_TERM_SIZE) {
642         snprintf(
643             loop_ctx->tab_complete_term,
644             MLE_LOOP_CTX_MAX_COMPLETE_TERM_SIZE,
645             "%.*s",
646             (int)ctx->bview->buffer->first_line->data_len,
647             ctx->bview->buffer->first_line->data
648         );
649         loop_ctx->tab_complete_index = 0;
650     } else {
651         return MLE_OK;
652     }
653 
654      // Assemble compgen command
655     cmd_arg = util_escape_shell_arg(
656         loop_ctx->tab_complete_term,
657         strlen(loop_ctx->tab_complete_term)
658     );
659     asprintf(&cmd, "compgen -f %s 2>/dev/null | sort", cmd_arg);
660 
661     // Run compgen command
662     terms = NULL;
663     terms_len = 0;
664     util_shell_exec(ctx->editor, cmd, 1, NULL, 0, 0, "bash", &terms, &terms_len);
665     free(cmd);
666     free(cmd_arg);
667 
668     // Get number of terms
669     // TODO valgrind thinks there's an error here
670     num_terms = 0;
671     term = strchr(terms, '\n');
672     while (term) {
673         num_terms += 1;
674         term = strchr(term + 1, '\n');
675     }
676 
677     // Bail if no terms
678     if (num_terms < 1) {
679         free(terms);
680         return MLE_OK;
681     }
682 
683     // Determine term index
684     term_index = loop_ctx->tab_complete_index % num_terms;
685 
686     // Set prompt input to term
687     term = strtok(terms, "\n");
688     while (term != NULL) {
689         if (term_index == 0) {
690             buffer_set(ctx->bview->buffer, term, strlen(term));
691             mark_move_eol(ctx->cursor->mark);
692             break;
693         } else {
694             term_index -= 1;
695         }
696         term = strtok(NULL, "\n");
697     }
698 
699     free(terms);
700     return MLE_OK;
701 }
702 
703 // Find or add a prompt history entry for the current prompt
_editor_prompt_find_or_add_history(cmd_context_t * ctx,prompt_hnode_t ** optret_prompt_hnode)704 static prompt_history_t *_editor_prompt_find_or_add_history(cmd_context_t *ctx, prompt_hnode_t **optret_prompt_hnode) {
705     prompt_history_t *prompt_history;
706     HASH_FIND_STR(ctx->editor->prompt_history, ctx->bview->prompt_str, prompt_history);
707     if (!prompt_history) {
708         prompt_history = calloc(1, sizeof(prompt_history_t));
709         prompt_history->prompt_str = strdup(ctx->bview->prompt_str);
710         HASH_ADD_KEYPTR(hh, ctx->editor->prompt_history, prompt_history->prompt_str, strlen(prompt_history->prompt_str), prompt_history);
711     }
712     if (!ctx->loop_ctx->prompt_hnode) {
713         ctx->loop_ctx->prompt_hnode = prompt_history->prompt_hlist
714             ? prompt_history->prompt_hlist->prev
715             : NULL;
716     }
717     if (optret_prompt_hnode) {
718         *optret_prompt_hnode = ctx->loop_ctx->prompt_hnode;
719     }
720     return prompt_history;
721 }
722 
723 // Prompt history up
_editor_prompt_history_up(cmd_context_t * ctx)724 static int _editor_prompt_history_up(cmd_context_t *ctx) {
725     prompt_hnode_t *prompt_hnode;
726     _editor_prompt_find_or_add_history(ctx, &prompt_hnode);
727     if (prompt_hnode) {
728         ctx->loop_ctx->prompt_hnode = prompt_hnode->prev;
729         buffer_set(ctx->buffer, prompt_hnode->data, prompt_hnode->data_len);
730     }
731     return MLE_OK;
732 }
733 
734 // Prompt history down
_editor_prompt_history_down(cmd_context_t * ctx)735 static int _editor_prompt_history_down(cmd_context_t *ctx) {
736     prompt_hnode_t *prompt_hnode;
737     _editor_prompt_find_or_add_history(ctx, &prompt_hnode);
738     if (prompt_hnode) {
739         ctx->loop_ctx->prompt_hnode = prompt_hnode->next;
740         buffer_set(ctx->buffer, prompt_hnode->data, prompt_hnode->data_len);
741     }
742     return MLE_OK;
743 }
744 
745 // Prompt history append
_editor_prompt_history_append(cmd_context_t * ctx,char * data)746 static int _editor_prompt_history_append(cmd_context_t *ctx, char *data) {
747     prompt_history_t *prompt_history;
748     prompt_hnode_t *prompt_hnode;
749     prompt_history = _editor_prompt_find_or_add_history(ctx, NULL);
750     prompt_hnode = calloc(1, sizeof(prompt_hnode_t));
751     prompt_hnode->data = strdup(data);
752     prompt_hnode->data_len = (bint_t)strlen(data);
753     CDL_APPEND(prompt_history->prompt_hlist, prompt_hnode);
754     return MLE_OK;
755 }
756 
757 // Invoked when user hits a in a prompt_yna
_editor_prompt_yna_all(cmd_context_t * ctx)758 static int _editor_prompt_yna_all(cmd_context_t *ctx) {
759     ctx->loop_ctx->prompt_answer = MLE_PROMPT_ALL;
760     ctx->loop_ctx->should_exit = 1;
761     return MLE_OK;
762 }
763 
764 // Invoked when user hits y in a prompt_yn(a)
_editor_prompt_yn_yes(cmd_context_t * ctx)765 static int _editor_prompt_yn_yes(cmd_context_t *ctx) {
766     ctx->loop_ctx->prompt_answer = MLE_PROMPT_YES;
767     ctx->loop_ctx->should_exit = 1;
768     return MLE_OK;
769 }
770 
771 // Invoked when user hits n in a prompt_yn(a)
_editor_prompt_yn_no(cmd_context_t * ctx)772 static int _editor_prompt_yn_no(cmd_context_t *ctx) {
773     ctx->loop_ctx->prompt_answer = MLE_PROMPT_NO;
774     ctx->loop_ctx->should_exit = 1;
775     return MLE_OK;
776 }
777 
778 // Invoked when user cancels (Ctrl-C) a prompt_(input|yn), or hits any key in a prompt_ok
_editor_prompt_cancel(cmd_context_t * ctx)779 static int _editor_prompt_cancel(cmd_context_t *ctx) {
780     ctx->loop_ctx->prompt_answer = NULL;
781     ctx->loop_ctx->should_exit = 1;
782     return MLE_OK;
783 }
784 
785 // Invoked when user hits enter in a menu
_editor_menu_submit(cmd_context_t * ctx)786 static int _editor_menu_submit(cmd_context_t *ctx) {
787     if (ctx->bview->menu_callback) return ctx->bview->menu_callback(ctx);
788     return MLE_OK;
789 }
790 
791 // Invoked when user hits C-c in a menu
_editor_menu_cancel(cmd_context_t * ctx)792 static int _editor_menu_cancel(cmd_context_t *ctx) {
793     if (ctx->bview->aproc) aproc_destroy(ctx->bview->aproc, 1);
794     return MLE_OK;
795 }
796 
797 // Invoked when user hits up in a prompt_menu
_editor_prompt_menu_up(cmd_context_t * ctx)798 static int _editor_prompt_menu_up(cmd_context_t *ctx) {
799     mark_move_vert(ctx->editor->active_edit->active_cursor->mark, -1);
800     bview_rectify_viewport(ctx->editor->active_edit);
801     return MLE_OK;
802 }
803 
804 // Invoked when user hits down in a prompt_menu
_editor_prompt_menu_down(cmd_context_t * ctx)805 static int _editor_prompt_menu_down(cmd_context_t *ctx) {
806     mark_move_vert(ctx->editor->active_edit->active_cursor->mark, 1);
807     bview_rectify_viewport(ctx->editor->active_edit);
808     return MLE_OK;
809 }
810 
811 // Invoked when user hits page-up in a prompt_menu
_editor_prompt_menu_page_up(cmd_context_t * ctx)812 static int _editor_prompt_menu_page_up(cmd_context_t *ctx) {
813     mark_move_vert(ctx->editor->active_edit->active_cursor->mark, -1 * ctx->editor->active_edit->rect_buffer.h);
814     bview_zero_viewport_y(ctx->editor->active_edit);
815     return MLE_OK;
816 }
817 
818 // Invoked when user hits page-down in a prompt_menu
_editor_prompt_menu_page_down(cmd_context_t * ctx)819 static int _editor_prompt_menu_page_down(cmd_context_t *ctx) {
820     mark_move_vert(ctx->editor->active_edit->active_cursor->mark, ctx->editor->active_edit->rect_buffer.h);
821     bview_zero_viewport_y(ctx->editor->active_edit);
822     return MLE_OK;
823 }
824 
825 // Invoked when user hits down in a prompt_isearch
_editor_prompt_isearch_next(cmd_context_t * ctx)826 static int _editor_prompt_isearch_next(cmd_context_t *ctx) {
827     if (ctx->editor->active_edit->isearch_rule) {
828         mark_move_next_cre_nudge(ctx->editor->active_edit->active_cursor->mark, ctx->editor->active_edit->isearch_rule->cre);
829         bview_center_viewport_y(ctx->editor->active_edit);
830     }
831     return MLE_OK;
832 }
833 
834 // Invoked when user hits up in a prompt_isearch
_editor_prompt_isearch_prev(cmd_context_t * ctx)835 static int _editor_prompt_isearch_prev(cmd_context_t *ctx) {
836     if (ctx->editor->active_edit->isearch_rule) {
837         mark_move_prev_cre(ctx->editor->active_edit->active_cursor->mark, ctx->editor->active_edit->isearch_rule->cre);
838         bview_center_viewport_y(ctx->editor->active_edit);
839     }
840     return MLE_OK;
841 }
842 
843 // Invoked when user hits up in a prompt_isearch
_editor_prompt_isearch_viewport_up(cmd_context_t * ctx)844 static int _editor_prompt_isearch_viewport_up(cmd_context_t *ctx) {
845     return bview_set_viewport_y(ctx->editor->active_edit, ctx->editor->active_edit->viewport_y - 5, 0);
846 }
847 
848 // Invoked when user hits up in a prompt_isearch
_editor_prompt_isearch_viewport_down(cmd_context_t * ctx)849 static int _editor_prompt_isearch_viewport_down(cmd_context_t *ctx) {
850     return bview_set_viewport_y(ctx->editor->active_edit, ctx->editor->active_edit->viewport_y + 5, 0);
851 }
852 
853 // Drops a cursor on each isearch match
_editor_prompt_isearch_drop_cursors(cmd_context_t * ctx)854 static int _editor_prompt_isearch_drop_cursors(cmd_context_t *ctx) {
855     bview_t *bview;
856     mark_t *mark;
857     pcre *cre;
858     cursor_t *orig_cursor;
859     cursor_t *last_cursor;
860     bint_t nchars;
861     bview = ctx->editor->active_edit;
862     if (!bview->isearch_rule) return MLE_OK;
863     orig_cursor = bview->active_cursor;
864     mark = bview->active_cursor->mark;
865     cre = bview->isearch_rule->cre;
866     mark_move_beginning(mark);
867     last_cursor = NULL;
868     while (mark_move_next_cre_ex(mark, cre, NULL, NULL, &nchars) == MLBUF_OK) {
869         bview_add_cursor(bview, mark->bline, mark->col, &last_cursor);
870         mark_move_by(mark, MLE_MAX(1, nchars));
871     }
872     if (last_cursor) {
873         mark_join(orig_cursor->mark, last_cursor->mark);
874         bview_remove_cursor(bview, last_cursor);
875     }
876     bview->active_cursor = orig_cursor;
877     bview_center_viewport_y(bview);
878     ctx->loop_ctx->prompt_answer = NULL;
879     ctx->loop_ctx->should_exit = 1;
880     return MLE_OK;
881 }
882 
883 // Run editor loop
_editor_loop(editor_t * editor,loop_context_t * loop_ctx)884 static void _editor_loop(editor_t *editor, loop_context_t *loop_ctx) {
885     cmd_t *cmd;
886     cmd_context_t cmd_ctx;
887 
888     // Increment loop_depth
889     editor->loop_depth += 1;
890 
891     // Init cmd_context
892     memset(&cmd_ctx, 0, sizeof(cmd_context_t));
893     cmd_ctx.editor = editor;
894     cmd_ctx.loop_ctx = loop_ctx;
895 
896     // Loop until editor should exit
897     while (!loop_ctx->should_exit) {
898         // Set loop_ctx
899         editor->loop_ctx = loop_ctx;
900 
901         // Display editor
902         if (!editor->is_display_disabled) {
903             editor_display(editor);
904         }
905 
906         // Bail if debug_exit_after_startup set
907         if (editor->debug_exit_after_startup) {
908             break;
909         }
910 
911         // Check for async io
912         // aproc_drain_all will bail and return 0 if there's any tty data
913         if (editor->aprocs && aproc_drain_all(editor->aprocs, &editor->ttyfd)) {
914             continue;
915         }
916 
917         // Get input
918         if (editor_get_input(editor, loop_ctx, &cmd_ctx) == MLE_ERR) {
919             break;
920         }
921 
922         // Toggle macro?
923         if (_editor_maybe_toggle_macro(editor, &cmd_ctx.input)) {
924             continue;
925         }
926 
927         if ((cmd = _editor_get_command(editor, &cmd_ctx, NULL)) != NULL) {
928             // Found cmd in kmap trie, now execute
929             if (cmd_ctx.is_user_input && cmd->func == cmd_insert_data) {
930                 _editor_ingest_paste(editor, &cmd_ctx);
931             }
932             cmd_ctx.cmd = cmd;
933 
934             // Notify cmd:*:before observers
935             _editor_notify_cmd_observers(&cmd_ctx, 1);
936 
937             // Execute cmd
938             _editor_refresh_cmd_context(editor, &cmd_ctx);
939             cmd->func(&cmd_ctx);
940 
941             // Notify cmd:*:after observers
942             _editor_notify_cmd_observers(&cmd_ctx, 0);
943 
944             loop_ctx->binding_node = NULL;
945             loop_ctx->wildcard_params_len = 0;
946             loop_ctx->numeric_params_len = 0;
947             loop_ctx->last_cmd = cmd;
948         } else if (loop_ctx->need_more_input) {
949             // Need more input to find
950         } else {
951             // Not found, bad command
952             loop_ctx->binding_node = NULL;
953         }
954     }
955 
956     // Free pastebuf if present
957     if (cmd_ctx.pastebuf) free(cmd_ctx.pastebuf);
958 
959     // Free last_insert
960     str_free(&loop_ctx->last_insert);
961 
962     // Decrement loop_depth
963     editor->loop_depth -= 1;
964 }
965 
966 // Set fresh values on cmd_context
_editor_refresh_cmd_context(editor_t * editor,cmd_context_t * cmd_ctx)967 static void _editor_refresh_cmd_context(editor_t *editor, cmd_context_t *cmd_ctx) {
968     cmd_ctx->cursor = editor->active->active_cursor;
969     cmd_ctx->bview = cmd_ctx->cursor->bview;
970     cmd_ctx->buffer = cmd_ctx->bview->buffer;
971 }
972 
973 // Notify cmd observers
_editor_notify_cmd_observers(cmd_context_t * ctx,int is_before)974 static void _editor_notify_cmd_observers(cmd_context_t *ctx, int is_before) {
975     char *event_name;
976     asprintf(&event_name, "cmd:%s:%s", ctx->cmd->name, is_before ? "before" : "after");
977     _editor_refresh_cmd_context(ctx->editor, ctx);
978     editor_notify_observers(ctx->editor, event_name, (void*)ctx);
979     free(event_name);
980 }
981 
982 // Notify observers
editor_notify_observers(editor_t * editor,char * event_name,void * event_data)983 int editor_notify_observers(editor_t *editor, char *event_name, void *event_data) {
984     observer_t *observer;
985     // TODO implement as hash lookup
986     DL_FOREACH(editor->observers, observer) {
987         if (strcmp(event_name, observer->event_name) == 0) {
988             (observer->callback)(event_name, event_data, observer->udata);
989         }
990     }
991     return MLE_OK;
992 }
993 
994 // If input == editor->macro_toggle_key, toggle macro mode and return 1. Else
995 // return 0.
_editor_maybe_toggle_macro(editor_t * editor,kinput_t * input)996 static int _editor_maybe_toggle_macro(editor_t *editor, kinput_t *input) {
997     char *name;
998     if (memcmp(input, &editor->macro_toggle_key, sizeof(kinput_t)) != 0) {
999         return 0;
1000     }
1001     if (editor->is_recording_macro) {
1002         // Stop recording macro and add to map
1003         if (editor->macro_record->inputs_len > 0) {
1004             // Remove toggle key from macro inputs
1005             editor->macro_record->inputs_len -= 1; // TODO This is hacky
1006         }
1007         HASH_ADD_STR(editor->macro_map, name, editor->macro_record);
1008         editor->macro_record = NULL;
1009         editor->is_recording_macro = 0;
1010     } else {
1011         // Get macro name and start recording
1012         editor_prompt(editor, "record_macro: Name?", NULL, &name);
1013         if (!name) return 1;
1014         editor->macro_record = calloc(1, sizeof(kmacro_t));
1015         editor->macro_record->name = name;
1016         editor->is_recording_macro = 1;
1017     }
1018     return 1;
1019 }
1020 
1021 // Resize the editor
_editor_resize(editor_t * editor,int w,int h)1022 static void _editor_resize(editor_t *editor, int w, int h) {
1023     bview_t *bview;
1024     bview_rect_t *bounds;
1025 
1026     editor->w = w >= 0 ? w : tb_width();
1027     editor->h = h >= 0 ? h : tb_height();
1028 
1029     editor->rect_edit.x = 0;
1030     editor->rect_edit.y = 0;
1031     editor->rect_edit.w = editor->w;
1032     editor->rect_edit.h = editor->h - 2;
1033 
1034     editor->rect_status.x = 0;
1035     editor->rect_status.y = editor->h - 2;
1036     editor->rect_status.w = editor->w;
1037     editor->rect_status.h = 1;
1038 
1039     editor->rect_prompt.x = 0;
1040     editor->rect_prompt.y = editor->h - 1;
1041     editor->rect_prompt.w = editor->w;
1042     editor->rect_prompt.h = 1;
1043 
1044     DL_FOREACH2(editor->top_bviews, bview, top_next) {
1045         if (MLE_BVIEW_IS_PROMPT(bview)) {
1046             bounds = &editor->rect_prompt;
1047         } else if (MLE_BVIEW_IS_STATUS(bview)) {
1048             bounds = &editor->rect_status;
1049         } else {
1050             if (bview->split_parent) continue;
1051             bounds = &editor->rect_edit;
1052         }
1053         bview_resize(bview, bounds->x, bounds->y, bounds->w, bounds->h);
1054     }
1055 }
1056 
1057 // Draw bviews cursors recursively
_editor_draw_cursors(editor_t * editor,bview_t * bview)1058 static void _editor_draw_cursors(editor_t *editor, bview_t *bview) {
1059     if (MLE_BVIEW_IS_EDIT(bview) && bview_get_split_root(bview) != editor->active_edit_root) {
1060         return;
1061     }
1062     bview_draw_cursor(bview, bview == editor->active ? 1 : 0);
1063     if (bview->split_child) {
1064         _editor_draw_cursors(editor, bview->split_child);
1065     }
1066 }
1067 
1068 // Get user input
_editor_get_user_input(editor_t * editor,cmd_context_t * ctx)1069 static void _editor_get_user_input(editor_t *editor, cmd_context_t *ctx) {
1070     int rc;
1071     tb_event_t ev;
1072 
1073     // Reset pastebuf
1074     ctx->pastebuf_len = 0;
1075 
1076     // Use pastebuf_leftover is present
1077     if (ctx->has_pastebuf_leftover) {
1078         MLE_KINPUT_COPY(ctx->input, ctx->pastebuf_leftover);
1079         ctx->has_pastebuf_leftover = 0;
1080         return;
1081     }
1082 
1083     // Poll for event
1084     while (1) {
1085         rc = tb_poll_event(&ev);
1086         if (rc == -1) {
1087             continue; // Error
1088         } else if (rc == TB_EVENT_RESIZE) {
1089             // Resize
1090             _editor_resize(editor, ev.w, ev.h);
1091             editor_display(editor);
1092             continue;
1093         }
1094         MLE_KINPUT_SET(ctx->input, ev.mod, ev.ch, ev.key);
1095         break;
1096     }
1097 }
1098 
1099 // Ingest available input until non-cmd_insert_data
_editor_ingest_paste(editor_t * editor,cmd_context_t * ctx)1100 static void _editor_ingest_paste(editor_t *editor, cmd_context_t *ctx) {
1101     int rc;
1102     tb_event_t ev;
1103     kinput_t input;
1104     cmd_t *cmd;
1105 
1106     // Reset pastebuf
1107     ctx->pastebuf_len = 0;
1108 
1109     // Peek events
1110     while (1) {
1111         // Expand pastebuf if needed
1112         if (ctx->pastebuf_len + 1 > ctx->pastebuf_size) {
1113             ctx->pastebuf_size += MLE_PASTEBUF_INCR;
1114             ctx->pastebuf = realloc(ctx->pastebuf, sizeof(kinput_t) * ctx->pastebuf_size);
1115         }
1116 
1117         // Peek event
1118         rc = tb_peek_event(&ev, 0);
1119         if (rc == -1) {
1120             break; // Error
1121         } else if (rc == 0) {
1122             break; // Timeout
1123         } else if (rc == TB_EVENT_RESIZE) {
1124             // Resize
1125             _editor_resize(editor, ev.w, ev.h);
1126             editor_display(editor);
1127             break;
1128         }
1129         MLE_KINPUT_SET(input, ev.mod, ev.ch, ev.key);
1130         // TODO check for macro key
1131         cmd = _editor_get_command(editor, ctx, &input);
1132         if (cmd && cmd->func == cmd_insert_data) {
1133             // Insert data; keep ingesting
1134             ctx->pastebuf[ctx->pastebuf_len++] = input;
1135         } else {
1136             // Not insert data; set leftover and stop ingesting
1137             ctx->has_pastebuf_leftover = 1;
1138             ctx->pastebuf_leftover = input;
1139             break;
1140         }
1141     }
1142 }
1143 
1144 // Copy input into macro buffer
_editor_record_macro_input(kmacro_t * macro,kinput_t * input)1145 static void _editor_record_macro_input(kmacro_t *macro, kinput_t *input) {
1146     if (!macro->inputs) {
1147         macro->inputs = calloc(8, sizeof(kinput_t));
1148         macro->inputs_len = 0;
1149         macro->inputs_cap = 8;
1150     } else if (macro->inputs_len + 1 > macro->inputs_cap) {
1151         macro->inputs_cap = macro->inputs_len + 8;
1152         macro->inputs = realloc(macro->inputs, macro->inputs_cap * sizeof(kinput_t));
1153     }
1154     memcpy(macro->inputs + macro->inputs_len, input, sizeof(kinput_t));
1155     macro->inputs_len += 1;
1156 }
1157 
1158 // Return command for input
_editor_get_command(editor_t * editor,cmd_context_t * ctx,kinput_t * opt_peek_input)1159 static cmd_t *_editor_get_command(editor_t *editor, cmd_context_t *ctx, kinput_t *opt_peek_input) {
1160     loop_context_t *loop_ctx;
1161     kinput_t *input;
1162     kbinding_t *node;
1163     kbinding_t *binding;
1164     kmap_node_t *kmap_node;
1165     int is_top;
1166     int is_peek;
1167     int again;
1168 
1169     // Init some vars
1170     loop_ctx = ctx->loop_ctx;
1171     is_peek = opt_peek_input ? 1 : 0;
1172     input = opt_peek_input ? opt_peek_input : &ctx->input;
1173     kmap_node = editor->active->kmap_tail;
1174     node = loop_ctx->binding_node;
1175     is_top = (node == NULL ? 1 : 0);
1176     loop_ctx->need_more_input = 0;
1177     loop_ctx->binding_node = NULL;
1178 
1179     // Look for key binding
1180     while (kmap_node) {
1181         if (is_top) node = kmap_node->kmap->bindings;
1182         again = 0;
1183         binding = _editor_get_kbinding_node(node, input, loop_ctx, is_peek, &again);
1184         if (binding) {
1185             if (again) {
1186                 // Need more input on current node
1187                 if (!is_peek) {
1188                     loop_ctx->need_more_input = 1;
1189                     loop_ctx->binding_node = binding;
1190                 }
1191                 return NULL;
1192             } else if (binding->is_leaf) {
1193                 // Found leaf!
1194                 if (!is_peek) {
1195                     ctx->static_param = binding->static_param;
1196                 }
1197                 return _editor_resolve_cmd(editor, &(binding->cmd), binding->cmd_name);
1198             } else if (binding->children) {
1199                 // Need more input on next node
1200                 if (!is_peek) {
1201                     loop_ctx->need_more_input = 1;
1202                     loop_ctx->binding_node = binding;
1203                 }
1204                 return NULL;
1205             } else {
1206                 // This shouldn't happen... TODO err
1207                 return NULL;
1208             }
1209         } else if (node == kmap_node->kmap->bindings) {
1210             // Binding not found at top level
1211             if (kmap_node->kmap->default_cmd_name) {
1212                 // Fallback to default
1213                 return _editor_resolve_cmd(editor, &(kmap_node->kmap->default_cmd), kmap_node->kmap->default_cmd_name);
1214             }
1215             if (kmap_node->kmap->allow_fallthru && kmap_node != kmap_node->prev) {
1216                 // Fallback to previous kmap on stack
1217                 kmap_node = kmap_node->prev;
1218                 is_top = 1;
1219             } else {
1220                 // Fallback not allowed or reached bottom
1221                 return NULL;
1222             }
1223         } else {
1224             // Binding not found
1225             return NULL;
1226         }
1227     }
1228 
1229     // No more kmaps
1230     return NULL;
1231 }
1232 
1233 // Find binding by input in trie, taking into account numeric and wildcards patterns
_editor_get_kbinding_node(kbinding_t * node,kinput_t * input,loop_context_t * loop_ctx,int is_peek,int * ret_again)1234 static kbinding_t *_editor_get_kbinding_node(kbinding_t *node, kinput_t *input, loop_context_t *loop_ctx, int is_peek, int *ret_again) {
1235     kbinding_t *binding;
1236     kinput_t input_tmp;
1237     memset(&input_tmp, 0, sizeof(kinput_t));
1238 
1239     if (!is_peek) {
1240         // Look for numeric .. TODO can be more efficient about this
1241         if (input->ch >= '0' && input->ch <= '9') {
1242             if (!loop_ctx->numeric_node) {
1243                 MLE_KINPUT_SET_NUMERIC(input_tmp);
1244                 HASH_FIND(hh, node->children, &input_tmp, sizeof(kinput_t), binding);
1245                 loop_ctx->numeric_node = binding;
1246             }
1247             if (loop_ctx->numeric_node) {
1248                 if (loop_ctx->numeric_len < MLE_LOOP_CTX_MAX_NUMERIC_LEN) {
1249                     loop_ctx->numeric[loop_ctx->numeric_len] = (char)input->ch;
1250                     loop_ctx->numeric_len += 1;
1251                     *ret_again = 1;
1252                     return node; // Need more input on this node
1253                 }
1254                 return NULL; // Ran out of `numeric` buffer .. TODO err
1255             }
1256         }
1257 
1258         // Parse/reset numeric buffer
1259         if (loop_ctx->numeric_len > 0) {
1260             if (loop_ctx->numeric_params_len < MLE_LOOP_CTX_MAX_NUMERIC_PARAMS) {
1261                 loop_ctx->numeric[loop_ctx->numeric_len] = '\0';
1262                 loop_ctx->numeric_params[loop_ctx->numeric_params_len] = strtoul(loop_ctx->numeric, NULL, 10);
1263                 loop_ctx->numeric_params_len += 1;
1264                 loop_ctx->numeric_len = 0;
1265                 node = loop_ctx->numeric_node; // Resume on numeric's children
1266                 loop_ctx->numeric_node = NULL;
1267             } else {
1268                 loop_ctx->numeric_len = 0;
1269                 loop_ctx->numeric_node = NULL;
1270                 return NULL; // Ran out of `numeric_params` space .. TODO err
1271             }
1272         }
1273     }
1274 
1275     // Look for input
1276     HASH_FIND(hh, node->children, input, sizeof(kinput_t), binding);
1277     if (binding) {
1278         return binding;
1279     }
1280 
1281     if (!is_peek) {
1282         // Look for wildcard
1283         MLE_KINPUT_SET_WILDCARD(input_tmp);
1284         HASH_FIND(hh, node->children, &input_tmp, sizeof(kinput_t), binding);
1285         if (binding) {
1286             if (loop_ctx->wildcard_params_len < MLE_LOOP_CTX_MAX_WILDCARD_PARAMS) {
1287                 loop_ctx->wildcard_params[loop_ctx->wildcard_params_len] = input->ch;
1288                 loop_ctx->wildcard_params_len += 1;
1289             } else {
1290                 return NULL; // Ran out of `wildcard_params` space .. TODO err
1291             }
1292             return binding;
1293         }
1294     }
1295 
1296     return NULL;
1297 }
1298 
1299 // Resolve a potentially unresolved cmd by name
_editor_resolve_cmd(editor_t * editor,cmd_t ** rcmd,char * cmd_name)1300 static cmd_t *_editor_resolve_cmd(editor_t *editor, cmd_t **rcmd, char *cmd_name) {
1301     cmd_t *tcmd;
1302     cmd_t *cmd;
1303     cmd = NULL;
1304     if ((*rcmd)) {
1305         cmd = *rcmd;
1306     } else if (cmd_name) {
1307         HASH_FIND_STR(editor->cmd_map, cmd_name, tcmd);
1308         if (tcmd) {
1309             *rcmd = tcmd;
1310             cmd = tcmd;
1311         }
1312     }
1313     return cmd;
1314 }
1315 
1316 // Return a kinput_t given a key name
_editor_key_to_input(char * key,kinput_t * ret_input)1317 static int _editor_key_to_input(char *key, kinput_t *ret_input) {
1318     int keylen;
1319     int mod;
1320     uint32_t ch;
1321     keylen = strlen(key);
1322     memset(ret_input, 0, sizeof(kinput_t));
1323 
1324     // Check for special key
1325     #define MLE_KEY_DEF(pckey, pmod, pch, pkey) \
1326         } else if (keylen == strlen((pckey)) && !strncmp((pckey), key, keylen)) { \
1327             ret_input->mod = (pmod); \
1328             ret_input->ch = (pch); \
1329             ret_input->key = (pkey); \
1330             return MLE_OK;
1331     if (keylen < 1) {
1332         return MLE_ERR;
1333         #include "keys.h"
1334     }
1335     #undef MLE_KEY_DEF
1336 
1337     // Check for character, with potential ALT modifier
1338     mod = 0;
1339     ch = 0;
1340     if (keylen > 2 && !strncmp("M-", key, 2)) {
1341         mod = TB_MOD_ALT;
1342         key += 2;
1343     }
1344     utf8_char_to_unicode(&ch, key, NULL);
1345     if (ch < 1) {
1346         return MLE_ERR;
1347     }
1348     ret_input->mod = mod;
1349     ret_input->ch = ch;
1350     return MLE_OK;
1351 }
1352 
1353 // Init signal handlers
_editor_init_signal_handlers(editor_t * editor)1354 static void _editor_init_signal_handlers(editor_t *editor) {
1355     struct sigaction action;
1356     (void)editor;
1357     memset(&action, 0, sizeof(struct sigaction));
1358     action.sa_handler = _editor_graceful_exit;
1359     sigaction(SIGTERM, &action, NULL);
1360     sigaction(SIGINT, &action, NULL);
1361     sigaction(SIGQUIT, &action, NULL);
1362     sigaction(SIGHUP, &action, NULL);
1363     signal(SIGPIPE, SIG_IGN);
1364 }
1365 
1366 // Gracefully exit
_editor_graceful_exit(int signum)1367 static void _editor_graceful_exit(int signum) {
1368     bview_t *bview;
1369     char path[64];
1370     int bview_num;
1371     (void)signum;
1372     bview_num = 0;
1373     if (tb_width() >= 0) tb_shutdown();
1374     CDL_FOREACH2(_editor.all_bviews, bview, all_next) {
1375         if (bview->buffer->is_unsaved) {
1376             snprintf((char*)&path, 64, ".mle.bak.%d.%d", getpid(), bview_num);
1377             buffer_save_as(bview->buffer, path, NULL);
1378             bview_num += 1;
1379         }
1380     }
1381     editor_deinit(&_editor);
1382     exit(1);
1383 }
1384 
1385 // Register built-in commands
_editor_register_cmds(editor_t * editor)1386 static void _editor_register_cmds(editor_t *editor) {
1387     _editor_register_cmd_fn(editor, "cmd_anchor_by", cmd_anchor_by);
1388     _editor_register_cmd_fn(editor, "cmd_apply_macro_by", cmd_apply_macro_by);
1389     _editor_register_cmd_fn(editor, "cmd_apply_macro", cmd_apply_macro);
1390     _editor_register_cmd_fn(editor, "cmd_browse", cmd_browse);
1391     _editor_register_cmd_fn(editor, "cmd_close", cmd_close);
1392     _editor_register_cmd_fn(editor, "cmd_copy_by", cmd_copy_by);
1393     _editor_register_cmd_fn(editor, "cmd_copy", cmd_copy);
1394     _editor_register_cmd_fn(editor, "cmd_ctag", cmd_ctag);
1395     _editor_register_cmd_fn(editor, "cmd_cut_by", cmd_cut_by);
1396     _editor_register_cmd_fn(editor, "cmd_cut", cmd_cut);
1397     _editor_register_cmd_fn(editor, "cmd_delete_after", cmd_delete_after);
1398     _editor_register_cmd_fn(editor, "cmd_delete_before", cmd_delete_before);
1399     _editor_register_cmd_fn(editor, "cmd_delete_word_after", cmd_delete_word_after);
1400     _editor_register_cmd_fn(editor, "cmd_delete_word_before", cmd_delete_word_before);
1401     _editor_register_cmd_fn(editor, "cmd_drop_cursor_column", cmd_drop_cursor_column);
1402     _editor_register_cmd_fn(editor, "cmd_drop_sleeping_cursor", cmd_drop_sleeping_cursor);
1403     _editor_register_cmd_fn(editor, "cmd_find_word", cmd_find_word);
1404     _editor_register_cmd_fn(editor, "cmd_fsearch", cmd_fsearch);
1405     _editor_register_cmd_fn(editor, "cmd_fsearch_fzy", cmd_fsearch_fzy);
1406     _editor_register_cmd_fn(editor, "cmd_grep", cmd_grep);
1407     _editor_register_cmd_fn(editor, "cmd_indent", cmd_indent);
1408     _editor_register_cmd_fn(editor, "cmd_insert_data", cmd_insert_data);
1409     _editor_register_cmd_fn(editor, "cmd_insert_newline_above", cmd_insert_newline_above);
1410     _editor_register_cmd_fn(editor, "cmd_isearch", cmd_isearch);
1411     _editor_register_cmd_fn(editor, "cmd_jump", cmd_jump);
1412     _editor_register_cmd_fn(editor, "cmd_less", cmd_less);
1413     _editor_register_cmd_fn(editor, "cmd_move_beginning", cmd_move_beginning);
1414     _editor_register_cmd_fn(editor, "cmd_move_bol", cmd_move_bol);
1415     _editor_register_cmd_fn(editor, "cmd_move_bracket_back", cmd_move_bracket_back);
1416     _editor_register_cmd_fn(editor, "cmd_move_bracket_forward", cmd_move_bracket_forward);
1417     _editor_register_cmd_fn(editor, "cmd_move_bracket_toggle", cmd_move_bracket_toggle);
1418     _editor_register_cmd_fn(editor, "cmd_move_down", cmd_move_down);
1419     _editor_register_cmd_fn(editor, "cmd_move_end", cmd_move_end);
1420     _editor_register_cmd_fn(editor, "cmd_move_eol", cmd_move_eol);
1421     _editor_register_cmd_fn(editor, "cmd_move_left", cmd_move_left);
1422     _editor_register_cmd_fn(editor, "cmd_move_page_down", cmd_move_page_down);
1423     _editor_register_cmd_fn(editor, "cmd_move_page_up", cmd_move_page_up);
1424     _editor_register_cmd_fn(editor, "cmd_move_relative", cmd_move_relative);
1425     _editor_register_cmd_fn(editor, "cmd_move_right", cmd_move_right);
1426     _editor_register_cmd_fn(editor, "cmd_move_to_line", cmd_move_to_line);
1427     _editor_register_cmd_fn(editor, "cmd_move_until_back", cmd_move_until_back);
1428     _editor_register_cmd_fn(editor, "cmd_move_until_forward", cmd_move_until_forward);
1429     _editor_register_cmd_fn(editor, "cmd_move_up", cmd_move_up);
1430     _editor_register_cmd_fn(editor, "cmd_move_word_back", cmd_move_word_back);
1431     _editor_register_cmd_fn(editor, "cmd_move_word_forward", cmd_move_word_forward);
1432     _editor_register_cmd_fn(editor, "cmd_next", cmd_next);
1433     _editor_register_cmd_fn(editor, "cmd_open_file", cmd_open_file);
1434     _editor_register_cmd_fn(editor, "cmd_open_new", cmd_open_new);
1435     _editor_register_cmd_fn(editor, "cmd_open_replace_file", cmd_open_replace_file);
1436     _editor_register_cmd_fn(editor, "cmd_open_replace_new", cmd_open_replace_new);
1437     _editor_register_cmd_fn(editor, "cmd_outdent", cmd_outdent);
1438     _editor_register_cmd_fn(editor, "cmd_perl", cmd_perl);
1439     _editor_register_cmd_fn(editor, "cmd_pop_kmap", cmd_pop_kmap);
1440     _editor_register_cmd_fn(editor, "cmd_prev", cmd_prev);
1441     _editor_register_cmd_fn(editor, "cmd_push_kmap", cmd_push_kmap);
1442     _editor_register_cmd_fn(editor, "cmd_quit", cmd_quit);
1443     _editor_register_cmd_fn(editor, "cmd_quit_without_saving", cmd_quit_without_saving);
1444     _editor_register_cmd_fn(editor, "cmd_redo", cmd_redo);
1445     _editor_register_cmd_fn(editor, "cmd_redraw", cmd_redraw);
1446     _editor_register_cmd_fn(editor, "cmd_remove_extra_cursors", cmd_remove_extra_cursors);
1447     _editor_register_cmd_fn(editor, "cmd_replace", cmd_replace);
1448     _editor_register_cmd_fn(editor, "cmd_save_as", cmd_save_as);
1449     _editor_register_cmd_fn(editor, "cmd_save", cmd_save);
1450     _editor_register_cmd_fn(editor, "cmd_search", cmd_search);
1451     _editor_register_cmd_fn(editor, "cmd_search_next", cmd_search_next);
1452     _editor_register_cmd_fn(editor, "cmd_set_opt", cmd_set_opt);
1453     _editor_register_cmd_fn(editor, "cmd_shell", cmd_shell);
1454     _editor_register_cmd_fn(editor, "cmd_show_help", cmd_show_help);
1455     _editor_register_cmd_fn(editor, "cmd_split_horizontal", cmd_split_horizontal);
1456     _editor_register_cmd_fn(editor, "cmd_split_vertical", cmd_split_vertical);
1457     _editor_register_cmd_fn(editor, "cmd_toggle_anchor", cmd_toggle_anchor);
1458     _editor_register_cmd_fn(editor, "cmd_uncut", cmd_uncut);
1459     _editor_register_cmd_fn(editor, "cmd_undo", cmd_undo);
1460     _editor_register_cmd_fn(editor, "cmd_viewport_bot", cmd_viewport_bot);
1461     _editor_register_cmd_fn(editor, "cmd_viewport_mid", cmd_viewport_mid);
1462     _editor_register_cmd_fn(editor, "cmd_viewport_toggle", cmd_viewport_toggle);
1463     _editor_register_cmd_fn(editor, "cmd_viewport_top", cmd_viewport_top);
1464     _editor_register_cmd_fn(editor, "cmd_wake_sleeping_cursors", cmd_wake_sleeping_cursors);
1465     _editor_register_cmd_fn(editor, "_editor_menu_cancel", _editor_menu_cancel);
1466     _editor_register_cmd_fn(editor, "_editor_menu_submit", _editor_menu_submit);
1467     _editor_register_cmd_fn(editor, "_editor_prompt_cancel", _editor_prompt_cancel);
1468     _editor_register_cmd_fn(editor, "_editor_prompt_history_down", _editor_prompt_history_down);
1469     _editor_register_cmd_fn(editor, "_editor_prompt_history_up", _editor_prompt_history_up);
1470     _editor_register_cmd_fn(editor, "_editor_prompt_input_complete", _editor_prompt_input_complete);
1471     _editor_register_cmd_fn(editor, "_editor_prompt_input_submit", _editor_prompt_input_submit);
1472     _editor_register_cmd_fn(editor, "_editor_prompt_isearch_drop_cursors", _editor_prompt_isearch_drop_cursors);
1473     _editor_register_cmd_fn(editor, "_editor_prompt_isearch_next", _editor_prompt_isearch_next);
1474     _editor_register_cmd_fn(editor, "_editor_prompt_isearch_prev", _editor_prompt_isearch_prev);
1475     _editor_register_cmd_fn(editor, "_editor_prompt_isearch_viewport_up", _editor_prompt_isearch_viewport_up);
1476     _editor_register_cmd_fn(editor, "_editor_prompt_isearch_viewport_down", _editor_prompt_isearch_viewport_down);
1477     _editor_register_cmd_fn(editor, "_editor_prompt_menu_down", _editor_prompt_menu_down);
1478     _editor_register_cmd_fn(editor, "_editor_prompt_menu_page_down", _editor_prompt_menu_page_down);
1479     _editor_register_cmd_fn(editor, "_editor_prompt_menu_page_up", _editor_prompt_menu_page_up);
1480     _editor_register_cmd_fn(editor, "_editor_prompt_menu_up", _editor_prompt_menu_up);
1481     _editor_register_cmd_fn(editor, "_editor_prompt_yna_all", _editor_prompt_yna_all);
1482     _editor_register_cmd_fn(editor, "_editor_prompt_yn_no", _editor_prompt_yn_no);
1483     _editor_register_cmd_fn(editor, "_editor_prompt_yn_yes", _editor_prompt_yn_yes);
1484 }
1485 
1486 // Init built-in kmaps
_editor_init_kmaps(editor_t * editor)1487 static void _editor_init_kmaps(editor_t *editor) {
1488     _editor_init_kmap(editor, &editor->kmap_normal, "mle_normal", "cmd_insert_data", 0, (kbinding_def_t[]){
1489         MLE_KBINDING_DEF("cmd_show_help", "F2"),
1490         MLE_KBINDING_DEF("cmd_delete_before", "backspace"),
1491         MLE_KBINDING_DEF("cmd_delete_before", "backspace2"),
1492         MLE_KBINDING_DEF("cmd_delete_after", "delete"),
1493         MLE_KBINDING_DEF("cmd_insert_newline_above", "C-\\"),
1494         MLE_KBINDING_DEF("cmd_move_bol", "C-a"),
1495         MLE_KBINDING_DEF("cmd_move_bol", "home"),
1496         MLE_KBINDING_DEF("cmd_move_eol", "C-e"),
1497         MLE_KBINDING_DEF("cmd_move_eol", "end"),
1498         MLE_KBINDING_DEF("cmd_move_beginning", "M-\\"),
1499         MLE_KBINDING_DEF("cmd_move_end", "M-/"),
1500         MLE_KBINDING_DEF("cmd_move_left", "left"),
1501         MLE_KBINDING_DEF("cmd_move_right", "right"),
1502         MLE_KBINDING_DEF("cmd_move_up", "up"),
1503         MLE_KBINDING_DEF("cmd_move_down", "down"),
1504         MLE_KBINDING_DEF("cmd_move_page_up", "page-up"),
1505         MLE_KBINDING_DEF("cmd_move_page_down", "page-down"),
1506         MLE_KBINDING_DEF("cmd_move_to_line", "M-g"),
1507         MLE_KBINDING_DEF_EX("cmd_move_relative", "M-y ## u", "up"),
1508         MLE_KBINDING_DEF_EX("cmd_move_relative", "M-y ## d", "down"),
1509         MLE_KBINDING_DEF("cmd_move_until_forward", "M-' **"),
1510         MLE_KBINDING_DEF("cmd_move_until_back", "M-; **"),
1511         MLE_KBINDING_DEF("cmd_move_word_forward", "M-f"),
1512         MLE_KBINDING_DEF("cmd_move_word_back", "M-b"),
1513         MLE_KBINDING_DEF("cmd_move_bracket_forward", "M-]"),
1514         MLE_KBINDING_DEF("cmd_move_bracket_back", "M-["),
1515         MLE_KBINDING_DEF("cmd_move_bracket_toggle", "M-="),
1516         MLE_KBINDING_DEF("cmd_search", "C-f"),
1517         MLE_KBINDING_DEF("cmd_search_next", "C-g"),
1518         MLE_KBINDING_DEF("cmd_find_word", "C-v"),
1519         MLE_KBINDING_DEF("cmd_isearch", "C-r"),
1520         MLE_KBINDING_DEF("cmd_replace", "C-t"),
1521         MLE_KBINDING_DEF("cmd_cut", "C-k"),
1522         MLE_KBINDING_DEF("cmd_copy", "M-k"),
1523         MLE_KBINDING_DEF("cmd_uncut", "C-u"),
1524         MLE_KBINDING_DEF("cmd_redraw", "M-x l"),
1525         MLE_KBINDING_DEF("cmd_less", "M-l"),
1526         MLE_KBINDING_DEF("cmd_viewport_top", "M-9"),
1527         MLE_KBINDING_DEF("cmd_viewport_toggle", "C-l"),
1528         MLE_KBINDING_DEF("cmd_viewport_bot", "M-0"),
1529         MLE_KBINDING_DEF("cmd_push_kmap", "M-x p"),
1530         MLE_KBINDING_DEF("cmd_pop_kmap", "M-x P"),
1531         MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c d", "bracket"),
1532         MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c w", "word"),
1533         MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c s", "word_back"),
1534         MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c f", "word_forward"),
1535         MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c a", "bol"),
1536         MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c e", "eol"),
1537         MLE_KBINDING_DEF_EX("cmd_copy_by", "C-c c", "string"),
1538         MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d d", "bracket"),
1539         MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d w", "word"),
1540         MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d s", "word_back"),
1541         MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d f", "word_forward"),
1542         MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d a", "bol"),
1543         MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d e", "eol"),
1544         MLE_KBINDING_DEF_EX("cmd_cut_by", "C-d c", "string"),
1545         MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 d", "bracket"),
1546         MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 w", "word"),
1547         MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 s", "word_back"),
1548         MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 f", "word_forward"),
1549         MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 a", "bol"),
1550         MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 e", "eol"),
1551         MLE_KBINDING_DEF_EX("cmd_anchor_by", "C-2 c", "string"),
1552         MLE_KBINDING_DEF("cmd_delete_word_before", "C-w"),
1553         MLE_KBINDING_DEF("cmd_delete_word_after", "M-d"),
1554         MLE_KBINDING_DEF("cmd_toggle_anchor", "M-a"),
1555         MLE_KBINDING_DEF("cmd_drop_sleeping_cursor", "C-/ ."),
1556         MLE_KBINDING_DEF("cmd_wake_sleeping_cursors", "C-/ a"),
1557         MLE_KBINDING_DEF("cmd_remove_extra_cursors", "C-/ /"),
1558         MLE_KBINDING_DEF("cmd_drop_cursor_column", "C-/ '"),
1559         MLE_KBINDING_DEF("cmd_apply_macro", "M-z"),
1560         MLE_KBINDING_DEF("cmd_apply_macro_by", "M-m **"),
1561         MLE_KBINDING_DEF("cmd_next", "M-n"),
1562         MLE_KBINDING_DEF("cmd_prev", "M-p"),
1563         MLE_KBINDING_DEF("cmd_split_vertical", "M-v"),
1564         MLE_KBINDING_DEF("cmd_split_horizontal", "M-h"),
1565         MLE_KBINDING_DEF("cmd_grep", "M-q"),
1566         MLE_KBINDING_DEF("cmd_fsearch", "C-p"),
1567         MLE_KBINDING_DEF("cmd_browse", "C-b"),
1568         MLE_KBINDING_DEF("cmd_undo", "C-z"),
1569         MLE_KBINDING_DEF("cmd_redo", "C-y"),
1570         MLE_KBINDING_DEF("cmd_save", "C-s"),
1571         MLE_KBINDING_DEF("cmd_save_as", "M-s"),
1572         MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o a", "tab_to_space"),
1573         MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o t", "tab_width"),
1574         MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o y", "syntax"),
1575         MLE_KBINDING_DEF_EX("cmd_set_opt", "M-o w", "soft_wrap"),
1576         MLE_KBINDING_DEF("cmd_open_new", "C-n"),
1577         MLE_KBINDING_DEF("cmd_open_file", "C-o"),
1578         MLE_KBINDING_DEF("cmd_open_replace_new", "C-q n"),
1579         MLE_KBINDING_DEF("cmd_open_replace_file", "C-q o"),
1580         MLE_KBINDING_DEF_EX("cmd_fsearch", "C-q p", "replace"),
1581         MLE_KBINDING_DEF("cmd_indent", "M-."),
1582         MLE_KBINDING_DEF("cmd_outdent", "M-,"),
1583         MLE_KBINDING_DEF("cmd_ctag", "F3"),
1584         MLE_KBINDING_DEF("cmd_shell", "M-e"),
1585         MLE_KBINDING_DEF("cmd_perl", "M-w"),
1586         MLE_KBINDING_DEF("cmd_jump", "M-j"),
1587         MLE_KBINDING_DEF("cmd_close", "M-c"),
1588         MLE_KBINDING_DEF("cmd_quit", "C-x"),
1589         MLE_KBINDING_DEF(NULL, NULL)
1590     });
1591     _editor_init_kmap(editor, &editor->kmap_prompt_input, "mle_prompt_input", NULL, 1, (kbinding_def_t[]){
1592         MLE_KBINDING_DEF("_editor_prompt_input_submit", "enter"),
1593         MLE_KBINDING_DEF("_editor_prompt_input_complete", "tab"),
1594         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"),
1595         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"),
1596         MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"),
1597         MLE_KBINDING_DEF("_editor_prompt_history_up", "up"),
1598         MLE_KBINDING_DEF("_editor_prompt_history_down", "down"),
1599         MLE_KBINDING_DEF(NULL, NULL)
1600     });
1601     _editor_init_kmap(editor, &editor->kmap_prompt_yn, "mle_prompt_yn", NULL, 0, (kbinding_def_t[]){
1602         MLE_KBINDING_DEF("_editor_prompt_yn_yes", "y"),
1603         MLE_KBINDING_DEF("_editor_prompt_yn_no", "n"),
1604         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"),
1605         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"),
1606         MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"),
1607         MLE_KBINDING_DEF(NULL, NULL)
1608     });
1609     _editor_init_kmap(editor, &editor->kmap_prompt_yna, "mle_prompt_yna", NULL, 0, (kbinding_def_t[]){
1610         MLE_KBINDING_DEF("_editor_prompt_yn_yes", "y"),
1611         MLE_KBINDING_DEF("_editor_prompt_yn_no", "n"),
1612         MLE_KBINDING_DEF("_editor_prompt_yna_all", "a"),
1613         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"),
1614         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"),
1615         MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"),
1616         MLE_KBINDING_DEF(NULL, NULL)
1617     });
1618     _editor_init_kmap(editor, &editor->kmap_prompt_ok, "mle_prompt_ok", "_editor_prompt_cancel", 0, (kbinding_def_t[]){
1619         MLE_KBINDING_DEF(NULL, NULL)
1620     });
1621     _editor_init_kmap(editor, &editor->kmap_menu, "mle_menu", NULL, 1, (kbinding_def_t[]){
1622         MLE_KBINDING_DEF("_editor_menu_submit", "enter"),
1623         MLE_KBINDING_DEF("_editor_menu_cancel", "C-c"),
1624         MLE_KBINDING_DEF(NULL, NULL)
1625     });
1626     _editor_init_kmap(editor, &editor->kmap_prompt_menu, "mle_prompt_menu", NULL, 1, (kbinding_def_t[]){
1627         MLE_KBINDING_DEF("_editor_prompt_input_submit", "enter"),
1628         MLE_KBINDING_DEF("_editor_prompt_menu_up", "up"),
1629         MLE_KBINDING_DEF("_editor_prompt_menu_down", "down"),
1630         MLE_KBINDING_DEF("_editor_prompt_menu_up", "left"),
1631         MLE_KBINDING_DEF("_editor_prompt_menu_down", "right"),
1632         MLE_KBINDING_DEF("_editor_prompt_menu_page_up", "page-up"),
1633         MLE_KBINDING_DEF("_editor_prompt_menu_page_down", "page-down"),
1634         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"),
1635         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"),
1636         MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"),
1637         MLE_KBINDING_DEF(NULL, NULL)
1638     });
1639     _editor_init_kmap(editor, &editor->kmap_prompt_isearch, "mle_prompt_isearch", NULL, 1, (kbinding_def_t[]){
1640         MLE_KBINDING_DEF("_editor_prompt_isearch_prev", "up"),
1641         MLE_KBINDING_DEF("_editor_prompt_isearch_next", "down"),
1642         MLE_KBINDING_DEF("_editor_prompt_isearch_viewport_up", "page-up"),
1643         MLE_KBINDING_DEF("_editor_prompt_isearch_viewport_down", "page-down"),
1644         MLE_KBINDING_DEF("_editor_prompt_isearch_drop_cursors", "C-/"),
1645         MLE_KBINDING_DEF("_editor_prompt_cancel", "enter"),
1646         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-c"),
1647         MLE_KBINDING_DEF("_editor_prompt_cancel", "C-x"),
1648         MLE_KBINDING_DEF("_editor_prompt_cancel", "M-c"),
1649         MLE_KBINDING_DEF(NULL, NULL)
1650     });
1651 }
1652 
1653 // Init a single kmap
_editor_init_kmap(editor_t * editor,kmap_t ** ret_kmap,char * name,char * default_cmd_name,int allow_fallthru,kbinding_def_t * defs)1654 static void _editor_init_kmap(editor_t *editor, kmap_t **ret_kmap, char *name, char *default_cmd_name, int allow_fallthru, kbinding_def_t *defs) {
1655     kmap_t *kmap;
1656 
1657     kmap = calloc(1, sizeof(kmap_t));
1658     kmap->name = strdup(name);
1659     kmap->allow_fallthru = allow_fallthru;
1660     kmap->bindings = calloc(1, sizeof(kbinding_t));
1661     if (default_cmd_name) {
1662         kmap->default_cmd_name = strdup(default_cmd_name);
1663     }
1664 
1665     while (defs && defs->cmd_name) {
1666         _editor_init_kmap_add_binding(editor, kmap, defs);
1667         defs++;
1668     }
1669 
1670     HASH_ADD_KEYPTR(hh, editor->kmap_map, kmap->name, strlen(kmap->name), kmap);
1671     *ret_kmap = kmap;
1672 }
1673 
1674 // Add a binding to a kmap
_editor_init_kmap_add_binding(editor_t * editor,kmap_t * kmap,kbinding_def_t * binding_def)1675 static void _editor_init_kmap_add_binding(editor_t *editor, kmap_t *kmap, kbinding_def_t *binding_def) {
1676     char *cur_key_patt;
1677     cur_key_patt = strdup(binding_def->key_patt);
1678     _editor_init_kmap_add_binding_to_trie(&kmap->bindings->children, binding_def->cmd_name, cur_key_patt, binding_def->key_patt, binding_def->static_param);
1679     if (strcmp(binding_def->cmd_name, "cmd_show_help") == 0) {
1680         // TODO kind of hacky
1681         MLE_SET_INFO(editor, "show_help: Press %s", cur_key_patt);
1682     }
1683     free(cur_key_patt);
1684 }
1685 
1686 // Add a binding to a kmap trie
_editor_init_kmap_add_binding_to_trie(kbinding_t ** trie,char * cmd_name,char * cur_key_patt,char * full_key_patt,char * static_param)1687 static int _editor_init_kmap_add_binding_to_trie(kbinding_t **trie, char *cmd_name, char *cur_key_patt, char *full_key_patt, char *static_param) {
1688     char *next_key_patt;
1689     kbinding_t *node;
1690     kinput_t input;
1691 
1692     // Find next_key_patt and add null-char to cur_key_patt
1693     next_key_patt = strchr(cur_key_patt, ' ');
1694     if (next_key_patt != NULL) {
1695         *next_key_patt = '\0';
1696         next_key_patt += 1;
1697     }
1698     // cur_key_patt points to a null-term cstring now
1699     // next_key_patt is either NULL or points to a null-term cstring
1700 
1701     // Parse cur_key_patt token as input
1702     memset(&input, 0, sizeof(kinput_t));
1703     if (strcmp("##", cur_key_patt) == 0) {
1704         MLE_KINPUT_SET_NUMERIC(input);
1705     } else if (strcmp("**", cur_key_patt) == 0) {
1706         MLE_KINPUT_SET_WILDCARD(input);
1707     } else if (_editor_key_to_input(cur_key_patt, &input) == MLE_OK) {
1708         // Hi mom!
1709     } else {
1710         return MLE_ERR;
1711     }
1712 
1713     // Add node for input if it doesn't already exist
1714     node = NULL;
1715     HASH_FIND(hh, *trie, &input, sizeof(kinput_t), node);
1716     if (!node) {
1717         node = calloc(1, sizeof(kbinding_t));
1718         MLE_KINPUT_COPY(node->input, input);
1719         HASH_ADD(hh, *trie, input, sizeof(kinput_t), node);
1720     }
1721 
1722     if (next_key_patt) {
1723         // Recurse for next key
1724         if (_editor_init_kmap_add_binding_to_trie(&node->children, cmd_name, next_key_patt, full_key_patt, static_param) != MLE_OK) {
1725             free(node);
1726             return MLE_ERR;
1727         }
1728     } else {
1729         // Leaf node, set cmd
1730         node->static_param = static_param ? strdup(static_param) : NULL;
1731         node->key_patt = strdup(full_key_patt);
1732         node->cmd_name = strdup(cmd_name);
1733         node->is_leaf = 1;
1734     }
1735 
1736     return MLE_OK;
1737 }
1738 
1739 // Proxy for _editor_init_kmap with str in format '<name>,<default_cmd>,<allow_fallthru>'
_editor_init_kmap_by_str(editor_t * editor,kmap_t ** ret_kmap,char * str)1740 static int _editor_init_kmap_by_str(editor_t *editor, kmap_t **ret_kmap, char *str) {
1741     char *args[3];
1742     args[0] = strtok(str,  ","); if (!args[0]) return MLE_ERR;
1743     args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR;
1744     args[2] = strtok(NULL, ",");
1745     _editor_init_kmap(editor, ret_kmap, args[0], args[2] ? args[1] : NULL, atoi(args[2] ? args[2] : args[1]), NULL);
1746     return MLE_OK;
1747 }
1748 
1749 // Proxy for _editor_init_kmap_add_binding with str in format '<cmd>,<key>,<param>'
_editor_init_kmap_add_binding_by_str(editor_t * editor,kmap_t * kmap,char * str)1750 static int _editor_init_kmap_add_binding_by_str(editor_t *editor, kmap_t *kmap, char *str) {
1751     char *args[3];
1752     args[0] = strtok(str,  ","); if (!args[0]) return MLE_ERR;
1753     args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR;
1754     args[2] = strtok(NULL, ",");
1755     _editor_init_kmap_add_binding(editor, kmap, &((kbinding_def_t){args[0], args[1], args[2]}));
1756     return MLE_OK;
1757 }
1758 
1759 // Destroy a kmap
_editor_destroy_kmap(kmap_t * kmap,kbinding_t * trie)1760 static void _editor_destroy_kmap(kmap_t *kmap, kbinding_t *trie) {
1761     kbinding_t *binding;
1762     kbinding_t *binding_tmp;
1763     int is_top;
1764     is_top = (trie == kmap->bindings ? 1 : 0);
1765     HASH_ITER(hh, trie, binding, binding_tmp) {
1766         if (binding->children) {
1767             _editor_destroy_kmap(kmap, binding->children);
1768         }
1769         HASH_DELETE(hh, trie, binding);
1770         if (binding->static_param) free(binding->static_param);
1771         if (binding->cmd_name) free(binding->cmd_name);
1772         if (binding->key_patt) free(binding->key_patt);
1773         free(binding);
1774     }
1775     if (is_top) {
1776         if (kmap->name) free(kmap->name);
1777         free(kmap);
1778     }
1779 }
1780 
1781 // Add a macro by str with format '<name> <key1> <key2> ... <keyN>'
_editor_add_macro_by_str(editor_t * editor,char * str)1782 static int _editor_add_macro_by_str(editor_t *editor, char *str) {
1783     int has_input;
1784     char *token;
1785     kmacro_t *macro;
1786     kinput_t input;
1787 
1788     has_input = 0;
1789     macro = NULL;
1790 
1791     // Tokenize by space
1792     token = strtok(str, " ");
1793     while (token) {
1794         if (!macro) {
1795             // Make macro with <name> on first token
1796             macro = calloc(1, sizeof(kmacro_t));
1797             macro->name = strdup(token);
1798         } else {
1799             // Parse token as kinput_t
1800             if (_editor_key_to_input(token, &input) != MLE_OK) {
1801                 free(macro->name);
1802                 free(macro);
1803                 return MLE_ERR;
1804             }
1805             // Add kinput_t to macro
1806             _editor_record_macro_input(macro, &input);
1807             has_input = 1;
1808         }
1809         // Get next token
1810         token = strtok(NULL, " ");
1811     }
1812 
1813     // Add macro to map if has_input
1814     if (has_input) {
1815         HASH_ADD_KEYPTR(hh, editor->macro_map, macro->name, strlen(macro->name), macro);
1816         return MLE_OK;
1817     }
1818 
1819     // Fail
1820     if (macro) {
1821         free(macro->name);
1822         free(macro);
1823     }
1824     return MLE_ERR;
1825 }
1826 
1827 // Init built-in syntax map
_editor_init_syntaxes(editor_t * editor)1828 static void _editor_init_syntaxes(editor_t *editor) {
1829     _editor_init_syntax(editor, NULL, "syn_generic", "\\.(c|cc|cpp|h|hpp|php|py|rb|erb|sh|pl|go|js|java|jsp|lua|rs|zig)$", -1, -1, (srule_def_t[]){
1830         { "(?<![\\w%@$])("
1831           "abstract|alias|alignas|alignof|and|and_eq|arguments|array|as|asm|"
1832           "assert|auto|base|begin|bitand|bitor|bool|boolean|break|byte|"
1833           "callable|case|catch|chan|char|checked|class|clone|cmp|compl|const|"
1834           "const_cast|constexpr|continue|debugger|decimal|declare|decltype|"
1835           "def|default|defer|defined|del|delegate|delete|die|do|done|double|"
1836           "dynamic_cast|echo|elif|else|elseif|elsif|empty|end|enddeclare|"
1837           "endfor|endforeach|endif|endswitch|endwhile|ensure|enum|eq|esac|"
1838           "eval|event|except|exec|exit|exp|explicit|export|extends|extern|"
1839           "fallthrough|false|fi|final|finally|fixed|float|fn|for|foreach|"
1840           "friend|from|func|function|ge|global|go|goto|gt|if|implements|"
1841           "implicit|import|in|include|include_once|inline|instanceof|insteadof|"
1842           "int|interface|internal|is|isset|lambda|le|let|list|lock|long|lt|m|"
1843           "map|module|mutable|namespace|native|ne|new|next|nil|no|noexcept|not|"
1844           "not_eq|null|nullptr|object|operator|or|or_eq|out|override|package|"
1845           "params|pass|print|private|protected|public|q|qq|qr|qw|qx|raise|"
1846           "range|readonly|redo|ref|register|reinterpret_cast|require|"
1847           "require_once|rescue|retry|return|s|sbyte|sealed|select|self|short|"
1848           "signed|sizeof|stackalloc|static|static_assert|static_cast|"
1849           "strictfp|string|struct|sub|super|switch|synchronized|template|"
1850           "then|this|thread_local|throw|throws|time|tr|trait|transient|true|"
1851           "try|type|typedef|typeid|typename|typeof|uint|ulong|unchecked|"
1852           "undef|union|unless|unsafe|unset|unsigned|until|use|ushort|using|"
1853           "var|virtual|void|volatile|when|while|with|xor|xor_eq|y|yield"
1854           ")\\b", NULL, TB_GREEN, TB_DEFAULT },
1855         { "[(){}<>\\[\\].,;:?!+=/\\\\%^*-]", NULL, TB_RED | TB_BOLD, TB_DEFAULT },
1856         { "(?<!\\w)[\\%@$][a-zA-Z_$][a-zA-Z0-9_]*\\b", NULL, TB_GREEN, TB_DEFAULT },
1857         { "\\b[A-Z_][A-Z0-9_]*\\b", NULL, TB_RED | TB_BOLD, TB_DEFAULT },
1858         { "\\b(-?(0x)?[0-9]+|true|false|null)\\b", NULL, TB_BLUE | TB_BOLD, TB_DEFAULT },
1859         { "'([^']|\\')*?'", NULL, TB_YELLOW | TB_BOLD, TB_DEFAULT },
1860         { "\"(\\\"|[^\"])*?\"", NULL, TB_YELLOW | TB_BOLD, TB_DEFAULT },
1861         { "/" "/.*$", NULL, TB_CYAN, TB_DEFAULT },
1862         { "^\\s*#( .*|)$", NULL, TB_CYAN, TB_DEFAULT },
1863         { "^#!/.*$", NULL, TB_CYAN, TB_DEFAULT },
1864         { "/\\" "*", "\\*" "/", TB_CYAN, TB_DEFAULT },
1865         { "\\t+", NULL, TB_RED | TB_UNDERLINE, TB_DEFAULT },
1866         { "\\s+$", NULL, TB_GREEN | TB_UNDERLINE, TB_DEFAULT },
1867         { NULL, NULL, 0, 0 }
1868     });
1869 }
1870 
1871 // Init a single syntax
_editor_init_syntax(editor_t * editor,syntax_t ** optret_syntax,char * name,char * path_pattern,int tab_width,int tab_to_space,srule_def_t * defs)1872 static void _editor_init_syntax(editor_t *editor, syntax_t **optret_syntax, char *name, char *path_pattern, int tab_width, int tab_to_space, srule_def_t *defs) {
1873     syntax_t *syntax;
1874 
1875     syntax = calloc(1, sizeof(syntax_t));
1876     syntax->name = strdup(name);
1877     syntax->path_pattern = strdup(path_pattern);
1878     syntax->tab_width = tab_width >= 1 ? tab_width : -1; // -1 means default
1879     syntax->tab_to_space = tab_to_space >= 0 ? (tab_to_space ? 1 : 0) : -1;
1880 
1881     while (defs && defs->re) {
1882         _editor_init_syntax_add_rule(syntax, defs);
1883         defs++;
1884     }
1885     HASH_ADD_KEYPTR(hh, editor->syntax_map, syntax->name, strlen(syntax->name), syntax);
1886 
1887     if (optret_syntax) *optret_syntax = syntax;
1888 }
1889 
1890 // Proxy for _editor_init_syntax with str in format '<name>,<path_pattern>,<tab_width>,<tab_to_space>'
_editor_init_syntax_by_str(editor_t * editor,syntax_t ** ret_syntax,char * str)1891 static int _editor_init_syntax_by_str(editor_t *editor, syntax_t **ret_syntax, char *str) {
1892     char *args[4];
1893     args[0] = strtok(str,  ","); if (!args[0]) return MLE_ERR;
1894     args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR;
1895     args[2] = strtok(NULL, ","); if (!args[2]) return MLE_ERR;
1896     args[3] = strtok(NULL, ","); if (!args[3]) return MLE_ERR;
1897     _editor_init_syntax(editor, ret_syntax, args[0], args[1], atoi(args[2]), atoi(args[3]), NULL);
1898     return MLE_OK;
1899 }
1900 
1901 // Add rule to syntax
_editor_init_syntax_add_rule(syntax_t * syntax,srule_def_t * def)1902 static void _editor_init_syntax_add_rule(syntax_t *syntax, srule_def_t *def) {
1903     srule_node_t *node;
1904     node = calloc(1, sizeof(srule_node_t));
1905     if (def->re_end) {
1906         node->srule = srule_new_multi(def->re, strlen(def->re), def->re_end, strlen(def->re_end), def->fg, def->bg);
1907     } else {
1908         node->srule = srule_new_single(def->re, strlen(def->re), 0, def->fg, def->bg);
1909     }
1910     if (node->srule) DL_APPEND(syntax->srules, node);
1911 }
1912 
1913 // Proxy for _editor_init_syntax_add_rule with str in format '<start>,<end>,<fg>,<bg>' or '<regex>,<fg>,<bg>'
_editor_init_syntax_add_rule_by_str(syntax_t * syntax,char * str)1914 static int _editor_init_syntax_add_rule_by_str(syntax_t *syntax, char *str) {
1915     char *args[4];
1916     int style_i;
1917     args[0] = strtok(str,  ","); if (!args[0]) return MLE_ERR;
1918     args[1] = strtok(NULL, ","); if (!args[1]) return MLE_ERR;
1919     args[2] = strtok(NULL, ","); if (!args[2]) return MLE_ERR;
1920     args[3] = strtok(NULL, ",");
1921     style_i = args[3] ? 2 : 1;
1922     _editor_init_syntax_add_rule(syntax, &((srule_def_t){ args[0], style_i == 2 ? args[1] : NULL, atoi(args[style_i]), atoi(args[style_i + 1]) }));
1923     return MLE_OK;
1924 }
1925 
1926 // Destroy a syntax
_editor_destroy_syntax_map(syntax_t * map)1927 static void _editor_destroy_syntax_map(syntax_t *map) {
1928     syntax_t *syntax;
1929     syntax_t *syntax_tmp;
1930     srule_node_t *srule;
1931     srule_node_t *srule_tmp;
1932     HASH_ITER(hh, map, syntax, syntax_tmp) {
1933         HASH_DELETE(hh, map, syntax);
1934         DL_FOREACH_SAFE(syntax->srules, srule, srule_tmp) {
1935             DL_DELETE(syntax->srules, srule);
1936             srule_destroy(srule->srule);
1937             free(srule);
1938         }
1939         free(syntax->name);
1940         free(syntax->path_pattern);
1941         free(syntax);
1942     }
1943 }
1944 
1945 // Read rc file
_editor_init_from_rc_read(editor_t * editor,FILE * rc,char ** ret_rc_data,size_t * ret_rc_data_len)1946 static int _editor_init_from_rc_read(editor_t *editor, FILE *rc, char **ret_rc_data, size_t *ret_rc_data_len) {
1947     (void)editor;
1948     fseek(rc, 0L, SEEK_END);
1949     *ret_rc_data_len = (size_t)ftell(rc);
1950     fseek(rc, 0L, SEEK_SET);
1951     *ret_rc_data = malloc(*ret_rc_data_len + 1);
1952     fread(*ret_rc_data, *ret_rc_data_len, 1, rc);
1953     (*ret_rc_data)[*ret_rc_data_len] = '\0';
1954     return MLE_OK;
1955 }
1956 
1957 // Exec rc file, read stdout
_editor_init_from_rc_exec(editor_t * editor,char * rc_path,char ** ret_rc_data,size_t * ret_rc_data_len)1958 static int _editor_init_from_rc_exec(editor_t *editor, char *rc_path, char **ret_rc_data, size_t *ret_rc_data_len) {
1959     char buf[512];
1960     size_t nbytes;
1961     char *data;
1962     size_t data_len;
1963     size_t data_cap;
1964     FILE *fp;
1965 
1966     // Popen rc file
1967     if ((fp = popen(rc_path, "r")) == NULL) {
1968         MLE_RETURN_ERR(editor, "Failed to popen rc file %s", rc_path);
1969     }
1970 
1971     // Read output
1972     data = NULL;
1973     data_len = 0;
1974     data_cap = 0;
1975     while ((nbytes = fread(buf, 1, sizeof(buf), fp)) != 0) {
1976         if (data_len + nbytes >= data_cap) {
1977             data_cap += sizeof(buf);
1978             data = realloc(data, data_cap);
1979         }
1980         memmove(data + data_len, buf, nbytes);
1981         data_len += nbytes;
1982     }
1983 
1984     // Add null terminator
1985     if (data_len + 1 >= data_cap) {
1986         data_cap += 1;
1987         data = realloc(data, data_cap);
1988     }
1989     data[data_len] = '\0';
1990 
1991     // Return
1992     pclose(fp);
1993     *ret_rc_data = data;
1994     *ret_rc_data_len = data_len;
1995     return MLE_OK;
1996 }
1997 
1998 // Parse rc file
_editor_init_from_rc(editor_t * editor,FILE * rc,char * rc_path)1999 static int _editor_init_from_rc(editor_t *editor, FILE *rc, char *rc_path) {
2000     int rv;
2001     size_t rc_data_len;
2002     char *rc_data;
2003     char *rc_data_stop;
2004     char *eol;
2005     char *bol;
2006     int fargc;
2007     char **fargv;
2008     struct stat statbuf;
2009     rv = MLE_OK;
2010     rc_data = NULL;
2011     rc_data_len = 0;
2012 
2013     // Read or exec rc file
2014     if (fstat(fileno(rc), &statbuf) == 0 && statbuf.st_mode & S_IXUSR) {
2015         _editor_init_from_rc_exec(editor, rc_path, &rc_data, &rc_data_len);
2016     } else {
2017         _editor_init_from_rc_read(editor, rc, &rc_data, &rc_data_len);
2018     }
2019     rc_data_stop = rc_data + rc_data_len;
2020 
2021     // Make fargc, fargv
2022     int i;
2023     fargv = NULL;
2024     for (i = 0; i < 2; i++) {
2025         bol = rc_data;
2026         fargc = 1;
2027         while (bol < rc_data_stop) {
2028             eol = strchr(bol, '\n');
2029             if (!eol) eol = rc_data_stop - 1;
2030             if (*bol != ';') { // Treat semicolon lines as comments
2031                 if (fargv) {
2032                     *eol = '\0';
2033                     fargv[fargc] = bol;
2034                 }
2035                 fargc += 1;
2036             }
2037             bol = eol + 1;
2038         }
2039         if (!fargv) {
2040             if (fargc < 2) break; // No options
2041             fargv = malloc((fargc + 1) * sizeof(char*));
2042             fargv[0] = "mle";
2043             fargv[fargc] = NULL;
2044         }
2045     }
2046 
2047     // Parse args
2048     if (fargv) {
2049         rv = _editor_init_from_args(editor, fargc, fargv);
2050         free(fargv);
2051     }
2052 
2053     free(rc_data);
2054 
2055     return rv;
2056 }
2057 
2058 // Parse cli args
_editor_init_from_args(editor_t * editor,int argc,char ** argv)2059 static int _editor_init_from_args(editor_t *editor, int argc, char **argv) {
2060     int rv;
2061     kmap_t *cur_kmap;
2062     syntax_t *cur_syntax;
2063     uscript_t *uscript;
2064     int c;
2065     rv = MLE_OK;
2066 
2067     cur_kmap = NULL;
2068     cur_syntax = NULL;
2069     optind = 1;
2070     while (rv == MLE_OK && (c = getopt(argc, argv, "ha:b:c:H:i:K:k:l:M:m:Nn:p:S:s:t:vw:x:y:z:Q:")) != -1) {
2071         switch (c) {
2072             case 'h':
2073                 printf("mle version %s\n\n", MLE_VERSION);
2074                 printf("Usage: mle [options] [file:line]...\n\n");
2075                 printf("    -h           Show this message\n");
2076                 printf("    -a <1|0>     Enable/disable tab_to_space (default: %d)\n", MLE_DEFAULT_TAB_TO_SPACE);
2077                 printf("    -b <1|0>     Enable/disable highlight bracket pairs (default: %d)\n", MLE_DEFAULT_HILI_BRACKET_PAIRS);
2078                 printf("    -c <column>  Color column (default: -1, disabled)\n");
2079                 printf("    -H <1|0>     Enable/disable headless mode (default: 1 if no tty, else 0)\n");
2080                 printf("    -i <1|0>     Enable/disable auto_indent (default: %d)\n", MLE_DEFAULT_AUTO_INDENT);
2081                 printf("    -K <kdef>    Make a kmap definition (use with -k)\n");
2082                 printf("    -k <kbind>   Add key binding to current kmap definition (use after -K)\n");
2083                 printf("    -l <ltype>   Set linenum type (default: 0, absolute)\n");
2084                 printf("    -M <macro>   Add a macro\n");
2085                 printf("    -m <key>     Set macro toggle key (default: %s)\n", MLE_DEFAULT_MACRO_TOGGLE_KEY);
2086                 printf("    -N           Skip reading of rc file\n");
2087                 printf("    -n <kmap>    Set init kmap (default: mle_normal)\n");
2088                 printf("    -p <macro>   Set startup macro\n");
2089                 printf("    -S <syndef>  Make a syntax definition (use with -s)\n");
2090                 printf("    -s <synrule> Add syntax rule to current syntax definition (use after -S)\n");
2091                 printf("    -t <size>    Set tab size (default: %d)\n", MLE_DEFAULT_TAB_WIDTH);
2092                 printf("    -v           Print version and exit\n");
2093                 printf("    -w <1|0>     Enable/disable soft word wrap (default: %d)\n", MLE_DEFAULT_SOFT_WRAP);
2094                 printf("    -x <uscript> Run a Lua user script\n");
2095                 printf("    -y <syntax>  Set override syntax for files opened at start up\n");
2096                 printf("    -z <1|0>     Enable/disable trim_paste (default: %d)\n", MLE_DEFAULT_TRIM_PASTE);
2097                 printf("\n");
2098                 printf("    file         At start up, open file\n");
2099                 printf("    file:line    At start up, open file at line\n");
2100                 printf("    kdef         '<name>,<default_cmd>,<allow_fallthru>'\n");
2101                 printf("    kbind        '<cmd>,<key>,<param>'\n");
2102                 printf("    ltype        0=absolute, 1=relative, 2=both\n");
2103                 printf("    macro        '<name> <key1> <key2> ... <keyN>'\n");
2104                 printf("    syndef       '<name>,<path_pattern>,<tab_width>,<tab_to_space>'\n");
2105                 printf("    synrule      '<start>,<end>,<fg>,<bg>'\n");
2106                 printf("    fg,bg        0=default     1=black       2=red         3=green\n");
2107                 printf("                 4=yellow      5=blue        6=magenta     7=cyan\n");
2108                 printf("                 8=white       256=bold      512=underline 1024=reverse\n");
2109                 rv = MLE_ERR;
2110                 break;
2111             case 'a':
2112                 editor->tab_to_space = atoi(optarg) ? 1 : 0;
2113                 break;
2114             case 'b':
2115                 editor->highlight_bracket_pairs = atoi(optarg) ? 1 : 0;
2116                 break;
2117             case 'c':
2118                 editor->color_col = atoi(optarg);
2119                 break;
2120             case 'H':
2121                 editor->headless_mode = atoi(optarg) ? 1 : 0;
2122                 break;
2123             case 'i':
2124                 editor->auto_indent = atoi(optarg) ? 1 : 0;
2125                 break;
2126             case 'K':
2127                 if (_editor_init_kmap_by_str(editor, &cur_kmap, optarg) != MLE_OK) {
2128                     MLE_LOG_ERR("Could not init kmap by str: %s\n", optarg);
2129                     editor->exit_code = EXIT_FAILURE;
2130                     rv = MLE_ERR;
2131                 }
2132                 break;
2133             case 'k':
2134                 if (!cur_kmap || _editor_init_kmap_add_binding_by_str(editor, cur_kmap, optarg) != MLE_OK) {
2135                     MLE_LOG_ERR("Could not add key binding to kmap %p by str: %s\n", (void*)cur_kmap, optarg);
2136                     editor->exit_code = EXIT_FAILURE;
2137                     rv = MLE_ERR;
2138                 }
2139                 break;
2140             case 'l':
2141                 editor->linenum_type = atoi(optarg);
2142                 if (editor->linenum_type < 0 || editor->linenum_type > 2) editor->linenum_type = 0;
2143                 break;
2144             case 'M':
2145                 if (_editor_add_macro_by_str(editor, optarg) != MLE_OK) {
2146                     MLE_LOG_ERR("Could not add macro by str: %s\n", optarg);
2147                     editor->exit_code = EXIT_FAILURE;
2148                     rv = MLE_ERR;
2149                 }
2150                 break;
2151             case 'm':
2152                 if (_editor_set_macro_toggle_key(editor, optarg) != MLE_OK) {
2153                     MLE_LOG_ERR("Could not set macro key to: %s\n", optarg);
2154                     editor->exit_code = EXIT_FAILURE;
2155                     rv = MLE_ERR;
2156                 }
2157                 break;
2158             case 'N':
2159                 // See _editor_should_skip_rc
2160                 break;
2161             case 'n':
2162                 if (editor->kmap_init_name) free(editor->kmap_init_name);
2163                 editor->kmap_init_name = strdup(optarg);
2164                 break;
2165             case 'p':
2166                 if (editor->startup_macro_name) free(editor->startup_macro_name);
2167                 editor->startup_macro_name = strdup(optarg);
2168                 break;
2169             case 'S':
2170                 if (_editor_init_syntax_by_str(editor, &cur_syntax, optarg) != MLE_OK) {
2171                     MLE_LOG_ERR("Could not init syntax by str: %s\n", optarg);
2172                     editor->exit_code = EXIT_FAILURE;
2173                     rv = MLE_ERR;
2174                 }
2175                 break;
2176             case 's':
2177                 if (!cur_syntax || _editor_init_syntax_add_rule_by_str(cur_syntax, optarg) != MLE_OK) {
2178                     MLE_LOG_ERR("Could not add style rule to syntax %p by str: %s\n", (void*)cur_syntax, optarg);
2179                     editor->exit_code = EXIT_FAILURE;
2180                     rv = MLE_ERR;
2181                 }
2182                 break;
2183             case 't':
2184                 editor->tab_width = atoi(optarg);
2185                 break;
2186             case 'v':
2187                 printf("mle version %s\n", MLE_VERSION);
2188                 rv = MLE_ERR;
2189                 break;
2190             case 'w':
2191                 editor->soft_wrap = atoi(optarg);
2192                 break;
2193             case 'x':
2194                 // TODO
2195                 if (!(uscript = uscript_run(editor, optarg))) {
2196                     MLE_LOG_ERR("Failed to run uscript: %s\n", optarg);
2197                     editor->exit_code = EXIT_FAILURE;
2198                     rv = MLE_ERR;
2199                 } else {
2200                     DL_APPEND(editor->uscripts, uscript);
2201                 }
2202                 break;
2203             case 'y':
2204                 editor->syntax_override = optarg;
2205                 break;
2206             case 'z':
2207                 editor->trim_paste = atoi(optarg) ? 1 : 0;
2208                 break;
2209             case 'Q':
2210                 switch (*optarg) {
2211                     case 'q': editor->debug_exit_after_startup = 1; break;
2212                     case 'd': editor->debug_dump_state_on_exit = 1; break;
2213                 }
2214                 break;
2215             default:
2216                 rv = MLE_ERR;
2217                 break;
2218         }
2219     }
2220 
2221     return rv;
2222 }
2223 
2224 // Init status bar
_editor_init_status(editor_t * editor)2225 static void _editor_init_status(editor_t *editor) {
2226     editor->status = bview_new(editor, NULL, 0, NULL);
2227     editor->status->type = MLE_BVIEW_TYPE_STATUS;
2228     editor->rect_status.fg = TB_WHITE;
2229     editor->rect_status.bg = TB_BLACK;
2230 }
2231 
2232 // Init bviews
_editor_init_bviews(editor_t * editor,int argc,char ** argv)2233 static void _editor_init_bviews(editor_t *editor, int argc, char **argv) {
2234     int i;
2235     char *path;
2236     int path_len;
2237 
2238     // Open bviews
2239     if (optind >= argc) {
2240         // Open blank
2241         editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, NULL);
2242     } else {
2243         // Open files
2244         for (i = optind; i < argc; i++) {
2245             path = argv[i];
2246             path_len = strlen(path);
2247             editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, path, path_len, 1, 0, 0, NULL, NULL);
2248         }
2249     }
2250 }
2251 
2252 // Init headless mode
_editor_init_headless_mode(editor_t * editor)2253 static int _editor_init_headless_mode(editor_t *editor) {
2254     fd_set readfds;
2255     ssize_t nbytes;
2256     char buf[1024];
2257     bview_t *bview;
2258     int stdin_is_a_pipe;
2259 
2260     // Check if stdin is a pipe
2261     stdin_is_a_pipe = isatty(STDIN_FILENO) != 1 ? 1 : 0;
2262 
2263     // Bail if not in headless mode and stdin is not a pipe
2264     if (!editor->headless_mode && !stdin_is_a_pipe) return MLE_OK;
2265 
2266     // Ensure blank bview
2267     if (!editor->active_edit->buffer->path
2268         && editor->active_edit->buffer->byte_count == 0
2269     ) {
2270         bview = editor->active_edit;
2271     } else {
2272         editor_open_bview(editor, NULL, MLE_BVIEW_TYPE_EDIT, NULL, 0, 1, 0, 0, NULL, &bview);
2273     }
2274 
2275     // If stdin is a pipe, read into bview
2276     if (!stdin_is_a_pipe) return MLE_OK;
2277     do {
2278         FD_ZERO(&readfds);
2279         FD_SET(STDIN_FILENO, &readfds);
2280         select(STDIN_FILENO + 1, &readfds, NULL, NULL, NULL);
2281         nbytes = 0;
2282         if (FD_ISSET(STDIN_FILENO, &readfds)) {
2283             nbytes = read(STDIN_FILENO, &buf, 1024);
2284             if (nbytes > 0) {
2285                 mark_insert_before(bview->active_cursor->mark, buf, nbytes);
2286             }
2287         }
2288     } while (nbytes > 0);
2289 
2290     mark_move_beginning(bview->active_cursor->mark);
2291     return MLE_OK;
2292 }
2293 
2294 // Init startup macro if present
_editor_init_startup_macro(editor_t * editor)2295 static int _editor_init_startup_macro(editor_t *editor) {
2296     kmacro_t *macro;
2297     if (!editor->startup_macro_name) return MLE_OK;
2298     macro = NULL;
2299     HASH_FIND_STR(editor->macro_map, editor->startup_macro_name, macro);
2300     if (!macro) return MLE_ERR;
2301     editor->macro_apply = macro;
2302     editor->macro_apply_input_index = 0;
2303     return MLE_OK;
2304 }
2305