keys_command.c - ledit - Text editor (WIP)
HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
keys_command.c (33463B)
---
1 /* FIXME: remove CHECK_VIEW_LOCKED when it is confirmed that the new system works properly */
2 /* FIXME: Parse commands properly and allow combinations of commands */
3 /* FIXME: properly parse commands - in particular, shown an error if there are extra
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, size_t lang_index);
40 static int substitute_yes_all(ledit_view *view, char *key_text, size_t len, size_t lang_index);
41 static int substitute_no(ledit_view *view, char *key_text, size_t len, size_t lang_index);
42 static int substitute_no_all(ledit_view *view, char *key_text, size_t len, size_t lang_index);
43 static int edit_insert_text(ledit_view *view, char *key_text, size_t len, size_t lang_index);
44 static int edit_cursor_left(ledit_view *view, char *key_text, size_t len, size_t lang_index);
45 static int edit_cursor_right(ledit_view *view, char *key_text, size_t len, size_t lang_index);
46 static int edit_cursor_to_end(ledit_view *view, char *key_text, size_t len, size_t lang_index);
47 static int edit_cursor_to_beginning(ledit_view *view, char *key_text, size_t len, size_t lang_index);
48 static int edit_backspace(ledit_view *view, char *key_text, size_t len, size_t lang_index);
49 static int edit_delete(ledit_view *view, char *key_text, size_t len, size_t lang_index);
50 static int edit_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
51 static int edit_prevcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index);
52 static int edit_nextcommand(ledit_view *view, char *key_text, size_t len, size_t lang_index);
53 static int edit_prevsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index);
54 static int edit_nextsearch(ledit_view *view, char *key_text, size_t len, size_t lang_index);
55 static int editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
56 static int editsearchb_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index);
57 static int edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index);
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 l2);
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, size_t l2);
64 static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2);
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, size_t l2);
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_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
104 {"edit-cursor-left", &edit_cursor_left, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
105 {"edit-cursor-right", &edit_cursor_right, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
106 {"edit-cursor-to-beginning", &edit_cursor_to_beginning, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
107 {"edit-cursor-to-end", &edit_cursor_to_end, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
108 {"edit-delete", &edit_delete, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
109 {"edit-discard", &edit_discard, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
110 {"edit-insert-text", &edit_insert_text, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT|CMD_EDITSEARCH|CMD_EDITSEARCHB},
111 {"edit-next-command", &edit_nextcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
112 {"edit-next-search", &edit_nextsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB},
113 {"edit-previous-command", &edit_prevcommand, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
114 {"edit-previous-search", &edit_prevsearch, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH|CMD_EDITSEARCHB},
115 {"edit-submit", &edit_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDIT},
116 {"edit-submit-backwards-search", &editsearchb_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCHB},
117 {"edit-submit-search", &editsearch_submit, KEY_FLAG_LOCK_ALLOWED, CMD_EDITSEARCH},
118 {"substitute-no", &substitute_no, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE},
119 {"substitute-no-all", &substitute_no_all, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, CMD_SUBSTITUTE},
120 {"substitute-yes", &substitute_yes, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE},
121 {"substitute-yes-all", &substitute_yes_all, KEY_FLAG_JUMP_TO_CURSOR, CMD_SUBSTITUTE},
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_ALLOWED},
130 {"write-quit", &handle_write_quit, CMD_FLAG_OPTIONAL_RANGE|CMD_FLAG_LOCK_ALLOWED},
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->cap, 1));
168 hist->cmds = ledit_reallocarray(hist->cmds, cap, sizeof(char *));
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_error(view)
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 lang_index);
218
219 /* FIXME: USE LEN EVERYWHERE INSTEAD OF RELYING ON cmd BEING NUL-TERMINATED */
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 line */
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 actually
248 trying to write the file, but I don't care at the moment. */
249 int ret = 0;
250 if (!(ret = stat(filename, &sb)) && !force && stored &&
251 (sb.st_mtim.tv_sec != view->buffer->file_mtime.tv_sec ||
252 sb.st_mtim.tv_nsec != view->buffer->file_mtime.tv_nsec)) {
253 window_show_message_fmt(
254 view->window,
255 "%s: file modification time changed; use ! to override",
256 filename
257 );
258 return 1;
259 /* FIXME: I guess the file can still exist if stat returns an error,
260 but the writing itself will probably fail then as well. */
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, filename, &errstr)) {
269 window_show_message_fmt(view->window, "Error writing %s: %s", filename, errstr);
270 return 1;
271 } else {
272 /* FIXME: better message */
273 window_show_message_fmt(view->window, "Wrote file %s", filename);
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 or use ! to override", -1);
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->cur_index, view->display_offset);
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 or use ! to override", -1);
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_state.num);
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.search);
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_state.max_line, &sub_state.line, &sub_state.byte)) {
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, view->cur_line, view->cur_index);
392 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
393 } else if (view->mode == VISUAL) {
394 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
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->cur_index);
408 } else if (view->mode == VISUAL && theme->highlight_search) {
409 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + sub_state.slen);
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, sub_state.max_line, &sub_state.line, &sub_state.byte)) {
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)", sub_state.num);
465 if (view->mode == NORMAL) {
466 /* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */
467 view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
468 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
469 } else if (view->mode == VISUAL) {
470 view_set_selection(view, view->cur_line, view->cur_index, 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, "Ongoing substitution in other view.");
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 character! */
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 in range";
581 return 1;
582 }
583 *final *= 10;
584 if (SIZE_MAX - (*c - '0') < *final) {
585 *errstr_ret = "Integer overflow in range";
586 return 1;
587 }
588 *final += (*c - '0');
589 } else if ((s & START_LINENO) && (s & START_RANGE)) {
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 + 2, len - cur - 2);
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 ? view->sel.line1 : view->sel.line2;
608 } else if (*(c + 1) == '>' && view->sel_valid) {
609 l = view->sel.line1 > view->sel.line2 ? view->sel.line1 : view->sel.line2;
610 } else if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) {
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->lines_num || l2 > view->lines_num)) {
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, &l2_valid, &errstr)) {
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 & CMD_OPTIONAL_RANGE)) {
724 window_show_message(view->window, "Command does not take range", -1);
725 return 0;
726 } else if (view->lock_text && !(cur_cmds->cmds[i].cb->flags & CMD_FLAG_LOCK_ALLOWED)) {
727 window_show_message(view->window, view->lock_text, -1);
728 return 0;
729 }
730 return cur_cmds->cmds[i].cb->func(view, cur_str + text_len, l1, l2);
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_index) {
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 lang_index) {
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_index) {
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 lang_index) {
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 lang_index) {
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->window) + len
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 lang_index) {
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, size_t lang_index) {
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 lang_index) {
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 lang_index) {
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_index) {
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_index) {
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_index) {
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 lang_index) {
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.cmds[cmdhistory.cur], -1);
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 lang_index) {
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.cmds[cmdhistory.cur], -1);
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 lang_index) {
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, searchhistory.cmds[searchhistory.cur], -1);
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 lang_index) {
901 (void)key_text; (void)len; (void)lang_index;
902 if (searchhistory.len > 0 && searchhistory.cur < searchhistory.len - 1) {
903 searchhistory.cur++;
904 window_set_bottom_bar_realtext(view->window, searchhistory.cmds[searchhistory.cur], -1);
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 probably conflict
914 in some way if other support for visual mode (e.g. only search in selection) is added */
915 /* FIXME: support visual mode, i.e. change selection to new place? */
916 /* FIXME: maybe have separate setting to allow highlighting search just when in visual
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, &view->cur_index, &len);
923 ledit_theme *theme = config_get_theme();
924 /* FIXME: figure out key stack handling when modes are also changed here */
925 if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL || ret == SEARCH_WRAPPED)) {
926 view_set_mode(view, VISUAL);
927 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + len);
928 } else if (view->mode == VISUAL) {
929 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
930 } else if (view->mode == NORMAL) {
931 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
932 }
933 view_ensure_cursor_shown(view);
934 if (ret != SEARCH_NORMAL)
935 window_show_message(view->window, search_state_to_str(ret), -1);
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, &view->cur_index, &len);
943 ledit_theme *theme = config_get_theme();
944 if (theme->highlight_search && len > 0 && (ret == SEARCH_NORMAL || ret == SEARCH_WRAPPED)) {
945 view_set_mode(view, VISUAL);
946 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index + len);
947 } else if (view->mode == VISUAL) {
948 view_set_selection(view, view->cur_line, view->cur_index, view->cur_line, view->cur_index);
949 } if (view->mode == NORMAL) {
950 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
951 }
952 view_ensure_cursor_shown(view);
953 if (ret != SEARCH_NORMAL)
954 window_show_message(view->window, search_state_to_str(ret), -1);
955 }
956
957 static int
958 editsearch_submit(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
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_pos);
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 lang_index) {
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_pos);
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_index) {
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, char *buf, int n, int lang_index) {
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_type) &&
1019 ((!strncmp(cur_keys->keys[i].text, buf, n) &&
1020 match_key(cur_keys->keys[i].mods, key_state & ~ShiftMask)) ||
1021 cur_keys->keys[i].text[0] == '\0')) {
1022 flags = cur_keys->keys[i].cb->flags;
1023 if (!(flags & KEY_FLAG_LOCK_ALLOWED) && view->lock_text) {
1024 (void)view_locked_error(view);
1025 grabkey = 0;
1026 break;
1027 }
1028 grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index);
1029 found = 1;
1030 break;
1031 }
1032 } else if ((cur_keys->keys[i].modes & view->cur_command_type) &&
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->lock_text) {
1037 (void)view_locked_error(view);
1038 grabkey = 0;
1039 break;
1040 }
1041 grabkey = cur_keys->keys[i].cb->func(view, buf, (size_t)n, lang_index);
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_handler};
1051 else
1052 return (struct action){ACTION_NONE, NULL};
1053 }