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 }