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