keys_command.c - ledit - Text editor (WIP) | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/ledit.git (encrypted, but very slow) | |
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/… | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
keys_command.c (33463B) | |
--- | |
1 /* FIXME: remove CHECK_VIEW_LOCKED when it is confirmed that the new sys… | |
2 /* FIXME: Parse commands properly and allow combinations of commands */ | |
3 /* FIXME: properly parse commands - in particular, shown an error if the… | |
4 characters on the line */ | |
5 #include <stdio.h> | |
6 #include <ctype.h> | |
7 #include <stdlib.h> | |
8 #include <stdint.h> | |
9 #include <unistd.h> | |
10 | |
11 #include <X11/Xlib.h> | |
12 #include <X11/Xutil.h> | |
13 #include <pango/pangoxft.h> | |
14 #include <X11/extensions/Xdbe.h> | |
15 #include <X11/keysym.h> | |
16 #include <X11/XF86keysym.h> | |
17 #include <X11/cursorfont.h> | |
18 | |
19 #include "memory.h" | |
20 #include "common.h" | |
21 #include "txtbuf.h" | |
22 #include "undo.h" | |
23 #include "cache.h" | |
24 #include "window.h" | |
25 #include "buffer.h" | |
26 #include "view.h" | |
27 #include "search.h" | |
28 #include "cleanup.h" | |
29 #include "util.h" | |
30 | |
31 #include "keys.h" | |
32 #include "keys_command.h" | |
33 #include "configparser.h" | |
34 | |
35 /***********************************************************************… | |
36 * Declarations for all functions that can be used in the configuration.… | |
37 ***********************************************************************… | |
38 | |
39 static int substitute_yes(ledit_view *view, char *key_text, size_t len, … | |
40 static int substitute_yes_all(ledit_view *view, char *key_text, size_t l… | |
41 static int substitute_no(ledit_view *view, char *key_text, size_t len, s… | |
42 static int substitute_no_all(ledit_view *view, char *key_text, size_t le… | |
43 static int edit_insert_text(ledit_view *view, char *key_text, size_t len… | |
44 static int edit_cursor_left(ledit_view *view, char *key_text, size_t len… | |
45 static int edit_cursor_right(ledit_view *view, char *key_text, size_t le… | |
46 static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t l… | |
47 static int edit_cursor_to_beginning(ledit_view *view, char *key_text, si… | |
48 static int edit_backspace(ledit_view *view, char *key_text, size_t len, … | |
49 static int edit_delete(ledit_view *view, char *key_text, size_t len, siz… | |
50 static int edit_submit(ledit_view *view, char *key_text, size_t len, siz… | |
51 static int edit_prevcommand(ledit_view *view, char *key_text, size_t len… | |
52 static int edit_nextcommand(ledit_view *view, char *key_text, size_t len… | |
53 static int edit_prevsearch(ledit_view *view, char *key_text, size_t len,… | |
54 static int edit_nextsearch(ledit_view *view, char *key_text, size_t len,… | |
55 static int editsearch_submit(ledit_view *view, char *key_text, size_t le… | |
56 static int editsearchb_submit(ledit_view *view, char *key_text, size_t l… | |
57 static int edit_discard(ledit_view *view, char *key_text, size_t len, si… | |
58 | |
59 static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2… | |
60 static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2); | |
61 static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l… | |
62 static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2… | |
63 static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, siz… | |
64 static int handle_substitute(ledit_view *view, char *cmd, size_t l1, siz… | |
65 | |
66 /*********************************************** | |
67 * String-function mapping for config parsing. * | |
68 ***********************************************/ | |
69 | |
70 typedef enum { | |
71 KEY_FLAG_NONE = 0, | |
72 KEY_FLAG_JUMP_TO_CURSOR = 1, | |
73 KEY_FLAG_LOCK_ALLOWED = 2 | |
74 } command_key_cb_flags; | |
75 | |
76 typedef enum { | |
77 CMD_FLAG_NONE = 0, | |
78 CMD_FLAG_OPTIONAL_RANGE = 1, | |
79 CMD_FLAG_LOCK_ALLOWED = 2 | |
80 } command_cb_flags; | |
81 | |
82 typedef int (*command_key_cb_func)(ledit_view *, char *, size_t, size_t); | |
83 struct command_key_cb { | |
84 char *text; | |
85 command_key_cb_func func; | |
86 command_key_cb_flags flags; | |
87 command_mode allowed_modes; | |
88 }; | |
89 | |
90 typedef int (*command_cb_func)(ledit_view *view, char *cmd, size_t l1, s… | |
91 struct command_cb { | |
92 char *text; | |
93 command_cb_func func; | |
94 command_cb_flags flags; | |
95 }; | |
96 | |
97 int | |
98 command_key_cb_modemask_is_valid(command_key_cb *cb, command_mode modes)… | |
99 return (~cb->allowed_modes & modes) == 0; | |
100 } | |
101 | |
102 static command_key_cb command_key_cb_map[] = { | |
103 {"edit-backspace", &edit_backspace, KEY_FLAG_LOCK_ALLOWED, CMD_E… | |
104 {"edit-cursor-left", &edit_cursor_left, KEY_FLAG_LOCK_ALLOWED, C… | |
105 {"edit-cursor-right", &edit_cursor_right, KEY_FLAG_LOCK_ALLOWED,… | |
106 {"edit-cursor-to-beginning", &edit_cursor_to_beginning, KEY_FLAG… | |
107 {"edit-cursor-to-end", &edit_cursor_to_end, KEY_FLAG_LOCK_ALLOWE… | |
108 {"edit-delete", &edit_delete, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CM… | |
109 {"edit-discard", &edit_discard, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|… | |
110 {"edit-insert-text", &edit_insert_text, KEY_FLAG_LOCK_ALLOWED, C… | |
111 {"edit-next-command", &edit_nextcommand, KEY_FLAG_LOCK_ALLOWED, … | |
112 {"edit-next-search", &edit_nextsearch, KEY_FLAG_LOCK_ALLOWED, CM… | |
113 {"edit-previous-command", &edit_prevcommand, KEY_FLAG_LOCK_ALLOW… | |
114 {"edit-previous-search", &edit_prevsearch, KEY_FLAG_LOCK_ALLOWED… | |
115 {"edit-submit", &edit_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT}, | |
116 {"edit-submit-backwards-search", &editsearchb_submit, KEY_FLAG_L… | |
117 {"edit-submit-search", &editsearch_submit, KEY_FLAG_LOCK_ALLOWED… | |
118 {"substitute-no", &substitute_no, KEY_FLAG_JUMP_TO_CURSOR|KEY_FL… | |
119 {"substitute-no-all", &substitute_no_all, KEY_FLAG_JUMP_TO_CURSO… | |
120 {"substitute-yes", &substitute_yes, KEY_FLAG_JUMP_TO_CURSOR, CMD… | |
121 {"substitute-yes-all", &substitute_yes_all, KEY_FLAG_JUMP_TO_CUR… | |
122 }; | |
123 | |
124 static command_cb command_cb_map[] = { | |
125 {"close-view", &close_view, CMD_FLAG_LOCK_ALLOWED}, | |
126 {"create-view", &create_view, CMD_FLAG_LOCK_ALLOWED}, | |
127 {"quit", &handle_quit, CMD_FLAG_LOCK_ALLOWED}, | |
128 {"substitute", &handle_substitute, CMD_FLAG_OPTIONAL_RANGE}, | |
129 {"write", &handle_write, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_A… | |
130 {"write-quit", &handle_write_quit, CMD_FLAG_OPTIONAL_RANGE|CMD_F… | |
131 }; | |
132 | |
133 GEN_CB_MAP_HELPERS(command_key_cb_map, command_key_cb, text) | |
134 GEN_CB_MAP_HELPERS(command_cb_map, command_cb, text) | |
135 | |
136 /*************************************************** | |
137 * General global variables and utility functions. * | |
138 ***************************************************/ | |
139 | |
140 static struct { | |
141 char *search; | |
142 char *replace; | |
143 size_t slen; | |
144 size_t rlen; | |
145 size_t line; | |
146 size_t byte; | |
147 size_t old_line; | |
148 size_t old_byte; | |
149 size_t max_line; | |
150 int global; | |
151 int num; | |
152 int start_group; /* only set for the first replacement */ | |
153 } sub_state = {NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; | |
154 | |
155 typedef struct { | |
156 size_t len, cur, cap; | |
157 char **cmds; | |
158 } history; | |
159 | |
160 history cmdhistory = {0, 0, 0, NULL}; | |
161 | |
162 history searchhistory = {0, 0, 0, NULL}; | |
163 | |
164 static void | |
165 push_history(history *hist, char *cmd, size_t len) { | |
166 if (hist->len >= hist->cap) { | |
167 size_t cap = ideal_array_size(hist->cap, add_sz(hist->ca… | |
168 hist->cmds = ledit_reallocarray(hist->cmds, cap, sizeof(… | |
169 hist->cap = cap; | |
170 } | |
171 hist->cmds[hist->len] = ledit_strndup(cmd, len); | |
172 hist->len++; | |
173 hist->cur = hist->len; | |
174 } | |
175 | |
176 static void | |
177 push_cmdhistory(char *cmd, size_t len) { | |
178 push_history(&cmdhistory, cmd, len); | |
179 } | |
180 | |
181 static void | |
182 push_searchhistory(char *search, size_t len) { | |
183 push_history(&searchhistory, search, len); | |
184 } | |
185 | |
186 void | |
187 command_key_cleanup(void) { | |
188 free(sub_state.search); | |
189 free(sub_state.replace); | |
190 for (size_t i = 0; i < cmdhistory.len; i++) { | |
191 free(cmdhistory.cmds[i]); | |
192 } | |
193 for (size_t i = 0; i < searchhistory.len; i++) { | |
194 free(searchhistory.cmds[i]); | |
195 } | |
196 free(cmdhistory.cmds); | |
197 free(searchhistory.cmds); | |
198 } | |
199 | |
200 static int | |
201 view_locked_error(ledit_view *view) { | |
202 window_show_message(view->window, view->lock_text, -1); | |
203 return 0; | |
204 } | |
205 | |
206 #define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_… | |
207 | |
208 /******************************************************************** | |
209 * Functions for handling commands typed in line editor (:w, etc.). * | |
210 ********************************************************************/ | |
211 | |
212 static int parse_range( | |
213 ledit_view *view, char *cmd, size_t len, size_t *idx_ret, | |
214 size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid, | |
215 char **errstr_ret | |
216 ); | |
217 static int handle_cmd(ledit_view *view, char *cmd, size_t len, size_t la… | |
218 | |
219 /* FIXME: USE LEN EVERYWHERE INSTEAD OF RELYING ON cmd BEING NUL-TERMINA… | |
220 /* returns 1 on error, 0 otherwise */ | |
221 static int | |
222 handle_write_base(ledit_view *view, char *cmd) { | |
223 #if TEST | |
224 /* disallow normal file writing in test mode so no | |
225 file can accidentally be destroyed by fuzz testing */ | |
226 (void)view; | |
227 (void)cmd; | |
228 return 0; | |
229 #else | |
230 /* FIXME: allow writing only part of file */ | |
231 char *filename = view->buffer->filename; | |
232 int stored = 1; | |
233 int force = 0; | |
234 if (*cmd == '!') { | |
235 force = 1; | |
236 cmd++; | |
237 } | |
238 /* FIXME: string parsing instead of just taking the rest of the … | |
239 if (cmd[0] == ' ' && cmd[1] != '\0') { | |
240 filename = cmd + 1; | |
241 stored = 0; | |
242 } | |
243 /* FIXME: file locks */ | |
244 char *errstr = NULL; | |
245 if (filename) { | |
246 struct stat sb; | |
247 /* There technically is a race between checking stat and… | |
248 trying to write the file, but I don't care at the mom… | |
249 int ret = 0; | |
250 if (!(ret = stat(filename, &sb)) && !force && stored && | |
251 (sb.st_mtim.tv_sec != view->buffer->file_mtime.tv_se… | |
252 sb.st_mtim.tv_nsec != view->buffer->file_mtime.tv_n… | |
253 window_show_message_fmt( | |
254 view->window, | |
255 "%s: file modification time changed; use ! t… | |
256 filename | |
257 ); | |
258 return 1; | |
259 /* FIXME: I guess the file can still exist if stat retur… | |
260 but the writing itself will probably fail then as wel… | |
261 } else if (!ret && !force && !stored) { | |
262 window_show_message_fmt( | |
263 view->window, | |
264 "%s: file exists; use ! to override", | |
265 filename | |
266 ); | |
267 return 1; | |
268 } else if (buffer_write_to_filename(view->buffer, filena… | |
269 window_show_message_fmt(view->window, "Error wri… | |
270 return 1; | |
271 } else { | |
272 /* FIXME: better message */ | |
273 window_show_message_fmt(view->window, "Wrote fil… | |
274 /* update modification time */ | |
275 if (stat(filename, &sb)) { | |
276 /* FIXME: what should be done here? */ | |
277 } else { | |
278 view->buffer->file_mtime = sb.st_mtim; | |
279 } | |
280 } | |
281 } else { | |
282 window_show_message(view->window, "No file name", -1); | |
283 return 1; | |
284 } | |
285 return 0; | |
286 #endif | |
287 } | |
288 | |
289 static int | |
290 handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
291 (void)l1; | |
292 (void)l2; | |
293 handle_write_base(view, cmd); | |
294 return 0; | |
295 } | |
296 | |
297 static int | |
298 handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
299 (void)l1; | |
300 (void)l2; | |
301 int force = 0; | |
302 if (*cmd == '!') | |
303 force = 1; | |
304 if (view->buffer->modified && !force) { | |
305 window_show_message(view->window, "File modified; write … | |
306 } else { | |
307 ledit_cleanup(); | |
308 exit(0); | |
309 } | |
310 return 0; | |
311 } | |
312 | |
313 static int | |
314 create_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
315 (void)cmd; | |
316 (void)l1; | |
317 (void)l2; | |
318 buffer_add_view(view->buffer, view->mode, view->cur_line, view->… | |
319 return 0; | |
320 } | |
321 | |
322 static int | |
323 close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
324 (void)cmd; | |
325 (void)l1; | |
326 (void)l2; | |
327 /* FIXME: This will lead to problems if I add something that | |
328 requires access to the view after the command is handled. */ | |
329 int force = 0; | |
330 if (*cmd == '!') | |
331 force = 1; | |
332 ledit_buffer *buffer = view->buffer; | |
333 if (buffer->views_num == 1 && buffer->modified && !force) { | |
334 window_show_message(view->window, "File modified; write … | |
335 } else { | |
336 view->destroy = 1; | |
337 } | |
338 return 0; | |
339 } | |
340 | |
341 static int | |
342 handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
343 (void)l1; | |
344 (void)l2; | |
345 if (handle_write_base(view, cmd)) | |
346 return 0; | |
347 ledit_cleanup(); | |
348 exit(0); | |
349 return 0; | |
350 } | |
351 | |
352 static void | |
353 show_num_substituted(ledit_view *view) { | |
354 window_show_message_fmt(view->window, "%d substitution(s)", sub_… | |
355 } | |
356 | |
357 /* returns 1 when match was found, 0 otherwise */ | |
358 static int | |
359 next_replace_pos( | |
360 ledit_view *view, | |
361 size_t line, size_t byte, size_t max_line, | |
362 size_t *line_ret, size_t *byte_ret) { | |
363 size_t start_index = byte; | |
364 for (size_t i = line; i <= max_line; i++) { | |
365 ledit_line *ll = buffer_get_line(view->buffer, i); | |
366 buffer_normalize_line(ll); | |
367 char *pos = strstr(ll->text + start_index, sub_state.sea… | |
368 if (pos != NULL) { | |
369 *line_ret = i; | |
370 *byte_ret = (size_t)(pos - ll->text); | |
371 return 1; | |
372 } | |
373 start_index = 0; | |
374 } | |
375 return 0; | |
376 } | |
377 | |
378 /* returns whether keys should continue being captured */ | |
379 static int | |
380 move_to_next_substitution(ledit_view *view) { | |
381 ledit_theme *theme = config_get_theme(); | |
382 if (view->mode == NORMAL) | |
383 view_wipe_line_cursor_attrs(view, view->cur_line); | |
384 else if (view->mode == VISUAL) | |
385 view_wipe_selection(view); | |
386 if (!next_replace_pos(view, sub_state.line, sub_state.byte, sub_… | |
387 /* FIXME: why are these set here? */ | |
388 view->cur_line = sub_state.line; | |
389 view->cur_index = sub_state.byte; | |
390 if (view->mode == NORMAL) { | |
391 view->cur_index = view_get_legal_normal_pos(view… | |
392 view_set_line_cursor_attrs(view, view->cur_line,… | |
393 } else if (view->mode == VISUAL) { | |
394 view_set_selection(view, view->cur_line, view->c… | |
395 } | |
396 window_show_message(view->window, "No more matches", -1); | |
397 buffer_unlock_all_views(view->buffer); | |
398 return 0; | |
399 } | |
400 if (theme->highlight_search && view->mode != VISUAL) { | |
401 view_wipe_line_cursor_attrs(view, view->cur_line); | |
402 view_set_mode(view, VISUAL); | |
403 } | |
404 view->cur_line = sub_state.line; | |
405 view->cur_index = sub_state.byte; | |
406 if (view->mode == NORMAL) { | |
407 view_set_line_cursor_attrs(view, view->cur_line, view->c… | |
408 } else if (view->mode == VISUAL && theme->highlight_search) { | |
409 view_set_selection(view, view->cur_line, view->cur_index… | |
410 } | |
411 window_show_message(view->window, "Replace? (y/Y/n/N)", -1); | |
412 view_ensure_cursor_shown(view); | |
413 return 1; | |
414 } | |
415 | |
416 /* WARNING: sub_state must be set properly! */ | |
417 static void | |
418 substitute_single(ledit_view *view) { | |
419 ledit_range cur_range; | |
420 cur_range.line1 = sub_state.old_line; | |
421 cur_range.byte1 = sub_state.old_byte; | |
422 cur_range.line2 = sub_state.line; | |
423 cur_range.byte2 = sub_state.byte; | |
424 buffer_delete_with_undo_base( | |
425 view->buffer, cur_range, | |
426 sub_state.start_group, view->mode, | |
427 sub_state.line, sub_state.byte, | |
428 sub_state.line, sub_state.byte + sub_state.slen, NULL | |
429 ); | |
430 sub_state.start_group = 0; | |
431 cur_range.line1 = sub_state.line; | |
432 cur_range.byte1 = sub_state.byte; | |
433 buffer_insert_with_undo_base( | |
434 view->buffer, cur_range, 0, 0, view->mode, | |
435 sub_state.line, sub_state.byte, | |
436 sub_state.replace, sub_state.rlen, | |
437 NULL, NULL | |
438 ); | |
439 sub_state.num++; | |
440 } | |
441 | |
442 static void | |
443 substitute_all_remaining(ledit_view *view) { | |
444 if (view->mode == NORMAL) | |
445 view_wipe_line_cursor_attrs(view, view->cur_line); | |
446 else if (view->mode == VISUAL) | |
447 view_wipe_selection(view); | |
448 size_t min_line = SIZE_MAX; | |
449 while (next_replace_pos(view, sub_state.line, sub_state.byte, su… | |
450 if (sub_state.line < min_line) | |
451 min_line = sub_state.line; | |
452 substitute_single(view); | |
453 view->cur_line = sub_state.old_line = sub_state.line; | |
454 view->cur_index = sub_state.old_byte = sub_state.byte; | |
455 if (!sub_state.global) { | |
456 sub_state.line++; | |
457 sub_state.byte = 0; | |
458 } else { | |
459 sub_state.byte += sub_state.rlen; | |
460 } | |
461 } | |
462 if (min_line < view->lines_num) | |
463 buffer_recalc_all_views_from_line(view->buffer, min_line… | |
464 window_show_message_fmt(view->window, "Replaced %d occurrence(s)… | |
465 if (view->mode == NORMAL) { | |
466 /* this doesn't need to be added to the undo stack since… | |
467 view->cur_index = view_get_legal_normal_pos(view, view->… | |
468 view_set_line_cursor_attrs(view, view->cur_line, view->c… | |
469 } else if (view->mode == VISUAL) { | |
470 view_set_selection(view, view->cur_line, view->cur_index… | |
471 } | |
472 view_ensure_cursor_shown(view); | |
473 buffer_unlock_all_views(view->buffer); | |
474 } | |
475 | |
476 static int | |
477 handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
478 CHECK_VIEW_LOCKED(view); | |
479 size_t len = strlen(cmd); | |
480 char *sep = NULL; | |
481 if (len == 0) goto error; | |
482 char *sepend = next_utf8(cmd + 1); | |
483 size_t seplen = sepend - cmd; | |
484 sep = ledit_strndup(cmd, seplen); | |
485 cmd += seplen; | |
486 char *next = strstr(cmd, sep); | |
487 if (next == NULL) goto error; | |
488 *next = '\0'; | |
489 next += seplen; | |
490 char *last = strstr(next, sep); | |
491 if (last == NULL) goto error; | |
492 *last = '\0'; | |
493 last += seplen; | |
494 int confirm = 0, global = 0; | |
495 char *c = last; | |
496 while (*c != '\0') { | |
497 switch (*c) { | |
498 case 'c': | |
499 confirm = 1; | |
500 break; | |
501 case 'g': | |
502 global = 1; | |
503 break; | |
504 default: | |
505 goto error; | |
506 } | |
507 c++; | |
508 } | |
509 free(sep); | |
510 sep = NULL; | |
511 free(sub_state.search); | |
512 free(sub_state.replace); | |
513 sub_state.search = ledit_strdup(cmd); | |
514 sub_state.replace = ledit_strdup(next); | |
515 sub_state.slen = strlen(sub_state.search); | |
516 sub_state.rlen = strlen(sub_state.replace); | |
517 sub_state.global = global; | |
518 sub_state.line = l1; | |
519 sub_state.byte = 0; | |
520 sub_state.old_line = view->cur_line; | |
521 sub_state.old_byte = view->cur_index; | |
522 sub_state.max_line = l2; | |
523 sub_state.num = 0; | |
524 sub_state.start_group = 1; | |
525 | |
526 if (confirm) { | |
527 buffer_lock_all_views_except(view->buffer, view, "Ongoin… | |
528 view->cur_command_type = CMD_SUBSTITUTE; | |
529 return move_to_next_substitution(view); | |
530 } else { | |
531 substitute_all_remaining(view); | |
532 } | |
533 return 0; | |
534 error: | |
535 window_show_message(view->window, "Invalid command", -1); | |
536 free(sep); | |
537 return 0; | |
538 } | |
539 | |
540 enum cmd_type { | |
541 CMD_NORMAL, | |
542 CMD_OPTIONAL_RANGE | |
543 }; | |
544 | |
545 /* | |
546 . current line | |
547 $ last line | |
548 % all lines | |
549 */ | |
550 | |
551 /* NOTE: Marks are only recognized here if they are one unicode characte… | |
552 /* NOTE: Only the line range of the selection is used at the moment. */ | |
553 static int | |
554 parse_range( | |
555 ledit_view *view, char *cmd, size_t len, size_t *idx_ret, | |
556 size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid, | |
557 char **errstr_ret) { | |
558 *errstr_ret = ""; | |
559 enum { | |
560 START_LINENO = 1, | |
561 START_RANGE = 2, | |
562 IN_RANGE = 4, | |
563 IN_LINENO = 8 | |
564 } s = START_LINENO | START_RANGE; | |
565 size_t l1 = 0, l2 = 0; | |
566 *l1_valid = 0; | |
567 *l2_valid = 0; | |
568 size_t cur = 0; | |
569 char *c; | |
570 while (cur < len) { | |
571 c = &cmd[cur]; | |
572 if (isdigit(*c)) { | |
573 if (s & IN_LINENO) { | |
574 size_t *final = &l2; | |
575 if (!*l2_valid) { | |
576 final = &l1; | |
577 *l1_valid = 1; | |
578 } | |
579 if (SIZE_MAX / 10 < *final) { | |
580 *errstr_ret = "Integer overflow … | |
581 return 1; | |
582 } | |
583 *final *= 10; | |
584 if (SIZE_MAX - (*c - '0') < *final) { | |
585 *errstr_ret = "Integer overflow … | |
586 return 1; | |
587 } | |
588 *final += (*c - '0'); | |
589 } else if ((s & START_LINENO) && (s & START_RANG… | |
590 l1 = *c - '0'; | |
591 *l1_valid = 1; | |
592 s = IN_RANGE | IN_LINENO; | |
593 } else if ((s & START_LINENO)) { | |
594 l2 = *c - '0'; | |
595 *l2_valid = 1; | |
596 s = IN_LINENO; | |
597 } | |
598 } else if (*c == '\'' && (s & START_LINENO)) { | |
599 if (len - cur <= 2) { | |
600 *errstr_ret = "Invalid range"; | |
601 return 1; | |
602 } | |
603 size_t aftermark_idx = cur + 2 + next_utf8_len(c… | |
604 size_t marklen = aftermark_idx - (cur + 1); | |
605 size_t l, b; | |
606 if (*(c + 1) == '<' && view->sel_valid) { | |
607 l = view->sel.line1 < view->sel.line2 ? … | |
608 } else if (*(c + 1) == '>' && view->sel_valid) { | |
609 l = view->sel.line1 > view->sel.line2 ? … | |
610 } else if (buffer_get_mark(view->buffer, c + 1, … | |
611 *errstr_ret = "Invalid mark"; | |
612 return 1; | |
613 } | |
614 if (!*l1_valid) { | |
615 l1 = l + 1; | |
616 *l1_valid = 1; | |
617 } else { | |
618 l2 = l + 1; | |
619 *l2_valid = 1; | |
620 } | |
621 cur = aftermark_idx; | |
622 s = 0; | |
623 continue; | |
624 } else if (*c == ',' && !(s & START_RANGE)) { | |
625 if (*l1_valid && *l2_valid) { | |
626 *errstr_ret = "Invalid range"; | |
627 return 1; | |
628 } else { | |
629 s = START_LINENO; | |
630 } | |
631 } else if (*c == '%') { | |
632 if (s & START_RANGE) { | |
633 l1 = 1; | |
634 l2 = view->lines_num; | |
635 *l1_valid = *l2_valid = 1; | |
636 cur++; | |
637 s = 0; | |
638 break; | |
639 } else { | |
640 *errstr_ret = "Invalid range"; | |
641 return 1; | |
642 } | |
643 } else if (*c == '.') { | |
644 if (s & START_LINENO) { | |
645 if (!*l1_valid) { | |
646 l1 = view->cur_line + 1; | |
647 *l1_valid = 1; | |
648 } else { | |
649 l2 = view->cur_line + 1; | |
650 *l2_valid = 1; | |
651 } | |
652 s = 0; | |
653 } else { | |
654 *errstr_ret = "Invalid range"; | |
655 return 1; | |
656 } | |
657 } else if (*c == '$') { | |
658 if (s & START_LINENO) { | |
659 if (!*l1_valid) { | |
660 l1 = view->lines_num; | |
661 *l1_valid = 1; | |
662 } else { | |
663 l2 = view->lines_num; | |
664 *l2_valid = 1; | |
665 } | |
666 s = 0; | |
667 } else { | |
668 *errstr_ret = "Invalid range"; | |
669 return 1; | |
670 } | |
671 } else { | |
672 break; | |
673 } | |
674 cur++; | |
675 } | |
676 if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE)) { | |
677 *errstr_ret = "Invalid range"; | |
678 return 1; | |
679 } | |
680 if ((*l1_valid || *l2_valid) && (l1 == 0 || l2 == 0 || l1 > view… | |
681 *errstr_ret = "Invalid line number in range"; | |
682 return 1; | |
683 } | |
684 *idx_ret = cur; | |
685 /* ranges are given 1-indexed by user */ | |
686 *line1_ret = l1 - 1; | |
687 *line2_ret = l2 - 1; | |
688 return 0; | |
689 } | |
690 | |
691 static int | |
692 handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index) { | |
693 if (len < 1) | |
694 return 0; | |
695 push_cmdhistory(cmd, len); | |
696 size_t l1, l2; | |
697 int l1_valid, l2_valid; | |
698 char *errstr; | |
699 size_t start_idx; | |
700 if (parse_range(view, cmd, len, &start_idx, &l1, &l2, &l1_valid,… | |
701 window_show_message(view->window, errstr, -1); | |
702 return 0; | |
703 } | |
704 if (start_idx >= len) { | |
705 window_show_message(view->window, "Invalid command", -1); | |
706 return 0; | |
707 } | |
708 size_t rem_len = len - start_idx; | |
709 char *cur_str = cmd + start_idx; | |
710 int range_given = l1_valid && l2_valid; | |
711 if (!range_given) { | |
712 l1 = l2 = view->cur_line; | |
713 } | |
714 command_array *cur_cmds = config_get_commands(lang_index); | |
715 char *cmd_text; | |
716 size_t text_len; | |
717 for (size_t i = 0; i < cur_cmds->num_cmds; i++) { | |
718 cmd_text = cur_cmds->cmds[i].text; | |
719 text_len = strlen(cmd_text); | |
720 if (rem_len < text_len) | |
721 continue; | |
722 if (!strncmp(cmd_text, cur_str, text_len)) { | |
723 if (range_given && !(cur_cmds->cmds[i].cb->flags… | |
724 window_show_message(view->window, "Comma… | |
725 return 0; | |
726 } else if (view->lock_text && !(cur_cmds->cmds[i… | |
727 window_show_message(view->window, view->… | |
728 return 0; | |
729 } | |
730 return cur_cmds->cmds[i].cb->func(view, cur_str … | |
731 } | |
732 } | |
733 window_show_message(view->window, "Invalid command", -1); | |
734 return 0; | |
735 } | |
736 | |
737 /*********************************** | |
738 * Functions called on keypresses. * | |
739 ***********************************/ | |
740 | |
741 static int | |
742 substitute_yes(ledit_view *view, char *key_text, size_t len, size_t lang… | |
743 (void)key_text; (void)len; (void)lang_index; | |
744 substitute_single(view); | |
745 buffer_recalc_line(view->buffer, sub_state.line); | |
746 if (!sub_state.global) { | |
747 sub_state.line++; | |
748 sub_state.byte = 0; | |
749 } else { | |
750 sub_state.byte += sub_state.rlen; | |
751 } | |
752 int ret = move_to_next_substitution(view); | |
753 if (!ret) | |
754 show_num_substituted(view); | |
755 return ret; | |
756 } | |
757 | |
758 static int | |
759 substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t … | |
760 (void)key_text; (void)len; (void)lang_index; | |
761 substitute_all_remaining(view); | |
762 show_num_substituted(view); | |
763 return 0; | |
764 } | |
765 | |
766 static int | |
767 substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_… | |
768 (void)key_text; (void)len; (void)lang_index; | |
769 if (!sub_state.global) { | |
770 sub_state.line++; | |
771 sub_state.byte = 0; | |
772 } else { | |
773 sub_state.byte += sub_state.slen; | |
774 } | |
775 int ret = move_to_next_substitution(view); | |
776 if (!ret) | |
777 show_num_substituted(view); | |
778 return ret; | |
779 } | |
780 | |
781 static int | |
782 substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t l… | |
783 (void)key_text; (void)len; (void)lang_index; | |
784 buffer_unlock_all_views(view->buffer); | |
785 show_num_substituted(view); | |
786 return 0; | |
787 } | |
788 | |
789 static int | |
790 edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t la… | |
791 (void)lang_index; | |
792 /* FIXME: overflow */ | |
793 window_insert_bottom_bar_text(view->window, key_text, len); | |
794 window_set_bottom_bar_cursor( | |
795 view->window, ledit_window_get_bottom_bar_cursor(view->windo… | |
796 ); | |
797 return 1; | |
798 } | |
799 | |
800 static int | |
801 edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t … | |
802 (void)key_text; (void)len; (void)lang_index; | |
803 window_bottom_bar_cursor_to_end(view->window); | |
804 return 1; | |
805 } | |
806 | |
807 static int | |
808 edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, s… | |
809 (void)key_text; (void)len; (void)lang_index; | |
810 window_bottom_bar_cursor_to_beginning(view->window); | |
811 return 1; | |
812 } | |
813 | |
814 static int | |
815 edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t la… | |
816 (void)key_text; (void)len; (void)lang_index; | |
817 window_move_bottom_bar_cursor(view->window, -1); | |
818 return 1; | |
819 } | |
820 | |
821 static int | |
822 edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t l… | |
823 (void)key_text; (void)len; (void)lang_index; | |
824 window_move_bottom_bar_cursor(view->window, 1); | |
825 return 1; | |
826 } | |
827 | |
828 static int | |
829 edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang… | |
830 (void)key_text; (void)len; (void)lang_index; | |
831 window_delete_bottom_bar_char(view->window, -1); | |
832 return 1; | |
833 } | |
834 | |
835 static int | |
836 edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_in… | |
837 (void)key_text; (void)len; (void)lang_index; | |
838 window_delete_bottom_bar_char(view->window, 1); | |
839 return 1; | |
840 } | |
841 | |
842 static int | |
843 edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_in… | |
844 (void)key_text; (void)len; | |
845 window_set_bottom_bar_text_shown(view->window, 0); | |
846 char *text = window_get_bottom_bar_text(view->window); | |
847 int min_pos = window_get_bottom_bar_min_pos(view->window); | |
848 int textlen = strlen(text); | |
849 /* this should never happen */ | |
850 if (min_pos > textlen) { | |
851 textlen = 0; | |
852 } else { | |
853 textlen -= min_pos; | |
854 text += min_pos; | |
855 } | |
856 /* FIXME: this is hacky */ | |
857 char *cmd = ledit_strndup(text, textlen); | |
858 int ret = handle_cmd(view, cmd, (size_t)textlen, lang_index); | |
859 free(cmd); | |
860 return ret; | |
861 } | |
862 | |
863 static int | |
864 edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t la… | |
865 (void)key_text; (void)len; (void)lang_index; | |
866 if (cmdhistory.cur > 0) { | |
867 cmdhistory.cur--; | |
868 window_set_bottom_bar_realtext(view->window, cmdhistory.… | |
869 window_bottom_bar_cursor_to_end(view->window); | |
870 } | |
871 return 1; | |
872 } | |
873 | |
874 static int | |
875 edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t la… | |
876 (void)key_text; (void)len; (void)lang_index; | |
877 if (cmdhistory.len > 0 && cmdhistory.cur < cmdhistory.len - 1) { | |
878 cmdhistory.cur++; | |
879 window_set_bottom_bar_realtext(view->window, cmdhistory.… | |
880 } else { | |
881 cmdhistory.cur = cmdhistory.len; | |
882 window_set_bottom_bar_realtext(view->window, "", -1); | |
883 } | |
884 window_bottom_bar_cursor_to_end(view->window); | |
885 return 1; | |
886 } | |
887 | |
888 static int | |
889 edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lan… | |
890 (void)key_text; (void)len; (void)lang_index; | |
891 if (searchhistory.cur > 0) { | |
892 searchhistory.cur--; | |
893 window_set_bottom_bar_realtext(view->window, searchhisto… | |
894 window_bottom_bar_cursor_to_end(view->window); | |
895 } | |
896 return 1; | |
897 } | |
898 | |
899 static int | |
900 edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lan… | |
901 (void)key_text; (void)len; (void)lang_index; | |
902 if (searchhistory.len > 0 && searchhistory.cur < searchhistory.l… | |
903 searchhistory.cur++; | |
904 window_set_bottom_bar_realtext(view->window, searchhisto… | |
905 } else { | |
906 searchhistory.cur = searchhistory.len; | |
907 window_set_bottom_bar_realtext(view->window, "", -1); | |
908 } | |
909 window_bottom_bar_cursor_to_end(view->window); | |
910 return 1; | |
911 } | |
912 | |
913 /* FIXME: the current "highlight_search" support is a bit weird and will… | |
914 in some way if other support for visual mode (e.g. only search in sel… | |
915 /* FIXME: support visual mode, i.e. change selection to new place? */ | |
916 /* FIXME: maybe have separate setting to allow highlighting search just … | |
917 mode (i.e. don't switch to visual mode automatically) */ | |
918 void | |
919 search_next(ledit_view *view) { | |
920 view_wipe_line_cursor_attrs(view, view->cur_line); | |
921 size_t len = 0; | |
922 search_state ret = ledit_search_next(view, &view->cur_line, &vie… | |
923 ledit_theme *theme = config_get_theme(); | |
924 /* FIXME: figure out key stack handling when modes are also chan… | |
925 if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL … | |
926 view_set_mode(view, VISUAL); | |
927 view_set_selection(view, view->cur_line, view->cur_index… | |
928 } else if (view->mode == VISUAL) { | |
929 view_set_selection(view, view->cur_line, view->cur_index… | |
930 } else if (view->mode == NORMAL) { | |
931 view_set_line_cursor_attrs(view, view->cur_line, view->c… | |
932 } | |
933 view_ensure_cursor_shown(view); | |
934 if (ret != SEARCH_NORMAL) | |
935 window_show_message(view->window, search_state_to_str(re… | |
936 } | |
937 | |
938 void | |
939 search_prev(ledit_view *view) { | |
940 view_wipe_line_cursor_attrs(view, view->cur_line); | |
941 size_t len = 0; | |
942 search_state ret = ledit_search_prev(view, &view->cur_line, &vie… | |
943 ledit_theme *theme = config_get_theme(); | |
944 if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL … | |
945 view_set_mode(view, VISUAL); | |
946 view_set_selection(view, view->cur_line, view->cur_index… | |
947 } else if (view->mode == VISUAL) { | |
948 view_set_selection(view, view->cur_line, view->cur_index… | |
949 } if (view->mode == NORMAL) { | |
950 view_set_line_cursor_attrs(view, view->cur_line, view->c… | |
951 } | |
952 view_ensure_cursor_shown(view); | |
953 if (ret != SEARCH_NORMAL) | |
954 window_show_message(view->window, search_state_to_str(re… | |
955 } | |
956 | |
957 static int | |
958 editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t l… | |
959 (void)key_text; (void)len; (void)lang_index; | |
960 window_set_bottom_bar_text_shown(view->window, 0); | |
961 char *text = window_get_bottom_bar_text(view->window); | |
962 int min_pos = window_get_bottom_bar_min_pos(view->window); | |
963 int textlen = strlen(text); | |
964 /* this should always be the case */ | |
965 if (min_pos <= textlen) { | |
966 if (min_pos < textlen) | |
967 push_searchhistory(text + min_pos, textlen - min… | |
968 set_search_forward(text + min_pos); | |
969 search_next(view); | |
970 } else { | |
971 window_show_message( | |
972 view->window, | |
973 "Error in program. Tell lumidify about it.", -1 | |
974 ); | |
975 } | |
976 return 0; | |
977 } | |
978 | |
979 static int | |
980 editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t … | |
981 (void)key_text; (void)len; (void)lang_index; | |
982 window_set_bottom_bar_text_shown(view->window, 0); | |
983 char *text = window_get_bottom_bar_text(view->window); | |
984 int min_pos = window_get_bottom_bar_min_pos(view->window); | |
985 int textlen = strlen(text); | |
986 /* this should always be the case */ | |
987 if (min_pos <= textlen) { | |
988 if (min_pos < textlen) | |
989 push_searchhistory(text + min_pos, textlen - min… | |
990 set_search_backward(text + min_pos); | |
991 search_next(view); | |
992 } else { | |
993 window_show_message( | |
994 view->window, | |
995 "Error in program. Tell lumidify about it.", -1 | |
996 ); | |
997 } | |
998 return 0; | |
999 } | |
1000 | |
1001 static int | |
1002 edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_i… | |
1003 (void)view; (void)key_text; (void)lang_index; | |
1004 (void)len; | |
1005 window_set_bottom_bar_text_shown(view->window, 0); | |
1006 return 0; | |
1007 } | |
1008 | |
1009 struct action | |
1010 command_key_handler(ledit_view *view, unsigned int key_state, KeySym sym… | |
1011 command_key_array *cur_keys = config_get_command_keys(lang_index… | |
1012 size_t num_keys = cur_keys->num_keys; | |
1013 int grabkey = 1, found = 0; | |
1014 command_key_cb_flags flags = KEY_FLAG_NONE; | |
1015 for (size_t i = 0; i < num_keys; i++) { | |
1016 if (cur_keys->keys[i].text) { | |
1017 if (n > 0 && | |
1018 (cur_keys->keys[i].modes & view->cur_command… | |
1019 ((!strncmp(cur_keys->keys[i].text, buf, n) && | |
1020 match_key(cur_keys->keys[i].mods, key_stat… | |
1021 cur_keys->keys[i].text[0] == '\0')) { | |
1022 flags = cur_keys->keys[i].cb->flags; | |
1023 if (!(flags & KEY_FLAG_LOCK_ALLOWED) && … | |
1024 (void)view_locked_error(view); | |
1025 grabkey = 0; | |
1026 break; | |
1027 } | |
1028 grabkey = cur_keys->keys[i].cb->func(vie… | |
1029 found = 1; | |
1030 break; | |
1031 } | |
1032 } else if ((cur_keys->keys[i].modes & view->cur_command_… | |
1033 (cur_keys->keys[i].keysym == sym) && | |
1034 (match_key(cur_keys->keys[i].mods, key_state)… | |
1035 flags = cur_keys->keys[i].cb->flags; | |
1036 if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lo… | |
1037 (void)view_locked_error(view); | |
1038 grabkey = 0; | |
1039 break; | |
1040 } | |
1041 grabkey = cur_keys->keys[i].cb->func(view, buf, … | |
1042 found = 1; | |
1043 break; | |
1044 } | |
1045 } | |
1046 if (found && (flags & KEY_FLAG_JUMP_TO_CURSOR)) | |
1047 view_ensure_cursor_shown(view); | |
1048 /* FIXME: proper error on invalid key */ | |
1049 if (grabkey) | |
1050 return (struct action){ACTION_GRABKEY, &command_key_hand… | |
1051 else | |
1052 return (struct action){ACTION_NONE, NULL}; | |
1053 } |