sled.c (75685B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2022-2026 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include <assert.h> 6 #include <errno.h> 7 #include <fcntl.h> 8 #include <locale.h> 9 #include <signal.h> 10 #include <stdarg.h> 11 #include <stdint.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <sys/stat.h> 16 #include <unistd.h> 17 18 #define COPYRIGHT \ 19 (" This program is licensed under the terms of GNU GPL v3" \ 20 " or (at your option)\n" \ 21 " any later version. Copyright (C) 2022-2026 Strahinya Radich.\n" \ 22 " See the file LICENSE for exact copyright and license " \ 23 "details.\n" \ 24 " File termbox.h is based on termbox2 TUI library, (c) 2021" \ 25 " termbox developers,\n" \ 26 " which is released under the terms of Expat license, see the file" \ 27 " LICENSE.termbox.\n") 28 29 #define SLED_C_GLOBALS 30 #define TB_IMPL 31 32 #include "draw.h" 33 #include "sled.h" 34 #include "config.h" 35 #include "limit.h" 36 #include "util.h" 37 #include "version.h" 38 39 #define SEL_NOT_EMPTY(state) \ 40 ((state->sel_start.row != state->sel_end.row) \ 41 || (state->sel_start.col != state->sel_end.col)) 42 43 extern const char* errors[]; 44 extern const char* actions[]; 45 extern const char* mode_labels[]; 46 extern const char* go_to_dialog_title; 47 extern const char* prompt_dirty; 48 extern const char* prompt_overwrite; 49 extern const char* prompt_reread_dirty; 50 extern const char* replace_dialog_title; 51 extern const char* search_dialog_title; 52 53 static int 54 cancel_close(struct DrawState* state) 55 { 56 assert(state != NULL); 57 if (!state->error && !IN(state->mode, M_PROMPT) 58 && !IN(state->mode, M_HELP)) 59 { 60 if (SEL_NOT_EMPTY(state)) 61 clear_selection(state); 62 else 63 state->error = ERR_EXIT_KEY; 64 } 65 else 66 { 67 state->error = ERR_NONE; 68 *state->prompt = 0; 69 CLEAR(state->mode, M_PROMPT | M_HELP); 70 } 71 72 return 0; 73 } 74 75 static int 76 clear_selection(struct DrawState* state) 77 { 78 assert(state != NULL); 79 CLEAR(state->mode, M_SELECTION); 80 init_coord(&state->sel_start, 0, 0); 81 init_coord(&state->sel_end, 0, 0); 82 return 0; 83 } 84 85 #define MAXCOPYBUF 8192 86 87 static int 88 cp(const char* from, const char* to) 89 { 90 struct stat st; 91 struct timespec times[2]; 92 char buf[MAXCOPYBUF]; 93 int fromfd, tofd; 94 int nread = 0; 95 int nwritten = 0; 96 int result; 97 int saved_errno; 98 99 assert((from != NULL) && (to != NULL)); 100 101 tofd = 0; 102 fromfd = open(from, O_RDONLY); 103 if (fromfd < 0) 104 return 1; 105 if (fstat(fromfd, &st) < 0) 106 { 107 result = 1; 108 goto cp_cleanup; 109 } 110 111 tofd = open(to, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode); 112 if (tofd < 0) 113 return 1; 114 if (fchmod(tofd, st.st_mode) < 0) 115 { 116 result = 1; 117 goto cp_cleanup; 118 } 119 times[0] = st.st_atim; 120 times[1] = st.st_mtim; 121 122 result = 0; 123 while ((nread = read(fromfd, buf, MAXCOPYBUF)) > 0) 124 { 125 nwritten = write(tofd, buf, nread); 126 if (nwritten < nread) 127 { 128 result = 1; 129 goto cp_cleanup; 130 } 131 } 132 133 if (nread < 0) 134 { 135 result = 1; 136 goto cp_cleanup; 137 } 138 139 errno = 0; 140 if (futimens(tofd, times) < 0) 141 result = 1; 142 143 cp_cleanup: 144 saved_errno = errno; 145 if (tofd) 146 close(tofd); 147 close(fromfd); 148 errno = saved_errno; 149 return result; 150 } 151 152 static int 153 create_file(struct DrawState* state) 154 { 155 struct BufferLine* newchunk = NULL; 156 uint32_t* newtext = NULL; 157 158 assert(state != NULL); 159 newchunk = calloc(ALLOC_DELTA, sizeof(struct BufferLine)); 160 if (!newchunk) 161 { 162 state->running = 0; 163 return 1; 164 } 165 for (ssize_t row = 0; row < ALLOC_DELTA; row++) 166 { 167 init_bufferline(newchunk + row); 168 newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); 169 if (!newtext) 170 { 171 state->running = 0; 172 return 1; 173 } 174 newchunk[row].size = LINE_DEFAULT; 175 newchunk[row].text = newtext; 176 } 177 state->buffer = newchunk; 178 state->buffer_size = ALLOC_DELTA; 179 state->rows_count = 1; 180 recalculate_line_numbers(state); 181 state->coord.row = 0; 182 state->dirty = 1; 183 184 return 0; 185 } 186 187 static int 188 cut_to_paste(struct DrawState* state) 189 { 190 assert(state != NULL); 191 simple_yank(state); 192 erase_selection(state); 193 position_to_selection_start(state); 194 clear_selection(state); 195 return 0; 196 } 197 198 static char* 199 dirname(char* dir, char* path, const ssize_t dir_size) 200 { 201 char* p = NULL; 202 203 assert((dir != NULL) && (path != NULL) && (dir_size > 0)); 204 if (!*path) 205 return NULL; 206 207 MEMCCPY(dir, path, dir_size, p); 208 209 if (p > dir + 2 && *(p - 2) == '/') 210 *p = 0; 211 if (p > dir + 1) 212 p--; 213 214 while (p != dir && *p != '/') 215 { 216 p--; 217 if (p != dir && *p == '/') 218 { 219 *p = 0; 220 return dir; 221 } 222 else if (p == dir && *p == '/') 223 return dir; 224 } 225 dir[0] = '.'; 226 dir[1] = 0; 227 return dir; 228 } 229 230 static int 231 do_search_replace(struct DrawState* state) 232 { 233 uint32_t* buf = NULL; 234 uint32_t* temp = NULL; 235 uint32_t* before = NULL; 236 uint32_t* found = NULL; 237 uint32_t* after = NULL; 238 /* minimum size is LINE_DEFAULT to avoid 0-allocation */ 239 ssize_t bufsize = LINE_DEFAULT; /* size in uint32_t units */ 240 ssize_t buflen = 0; 241 ssize_t old_buflen = 0; 242 ssize_t search_len; 243 ssize_t input_len; 244 ssize_t after_len = 0; 245 int start_row = 0; 246 int end_row; 247 248 assert((state != NULL) && (state->search != NULL) 249 && (state->input != NULL)); 250 search_len = u32_strlen(state->search); 251 input_len = u32_strlen(state->input); 252 end_row = state->rows_count; 253 254 if (SEL_NOT_EMPTY(state)) 255 get_selection(&start_row, NULL, &end_row, NULL, state); 256 257 for (int row = start_row; row < end_row; row++) 258 if (state->buffer[row].size > bufsize) 259 bufsize = state->buffer[row].size; 260 261 buf = calloc(bufsize, sizeof(uint32_t)); 262 if (!buf) 263 goto do_search_replace_alloc_error; 264 265 for (int row = start_row; row < end_row; row++) 266 { 267 before = state->buffer[row].text; 268 found = (uint32_t*)u32_strstr(before, state->search); 269 270 if (!found) 271 continue; 272 273 state->dirty = 1; 274 *buf = 0; 275 *found = 0; 276 buflen = u32_strlen(before); 277 ENSURE_SIZE(buf, temp, bufsize, buflen + 1, buflen + 1, 278 do_search_replace_alloc_error, uint32_t); 279 U32_MEMCCPY(buf, before, bufsize, temp); 280 after = found + search_len; 281 *(after - 1) = 0; 282 while (found) 283 { 284 old_buflen = buflen; 285 buflen += input_len; 286 ENSURE_SIZE(buf, temp, bufsize, buflen + 1, buflen + 1, 287 do_search_replace_alloc_error, uint32_t); 288 U32_MEMCCPY((buf + old_buflen), state->input, 289 input_len + 1, temp); 290 before = after; 291 found = (uint32_t*)u32_strstr(before, state->search); 292 if (found) /* new found, after before */ 293 *found = 0; 294 after_len = u32_strlen(after); 295 old_buflen = buflen; 296 buflen += after_len; 297 ENSURE_SIZE(buf, temp, bufsize, buflen + 1, buflen + 1, 298 do_search_replace_alloc_error, uint32_t); 299 U32_MEMCCPY((buf + old_buflen), after, after_len + 1, 300 temp); 301 if (found) /* new found, after before */ 302 { 303 after = found + search_len; 304 *(after - 1) = 0; 305 } 306 } 307 U32_SAFE_COPY(state->buffer[row].text, temp, 308 state->buffer[row].size, buf, bufsize, 309 do_search_replace_alloc_error); 310 state->buffer[row].length = buflen; 311 } 312 free(buf); 313 return 0; 314 315 do_search_replace_alloc_error: 316 state->running = 0; 317 return 1; 318 } 319 320 static int 321 enter_command_mode(struct DrawState* state) 322 { 323 assert(state != NULL); 324 SET(state->mode, M_COMMAND); 325 return 0; 326 } 327 328 static int 329 enter_insert_mode(struct DrawState* state) 330 { 331 assert(state != NULL); 332 CLEAR(state->mode, M_COMMAND); 333 return 0; 334 } 335 336 static int 337 erase_backwards(struct DrawState* state) 338 { 339 struct BufferLine* buffer_line; 340 341 assert(state != NULL); 342 buffer_line = &state->buffer[state->coord.row]; 343 344 if (SEL_NOT_EMPTY(state)) 345 { 346 erase_selection(state); 347 position_to_selection_start(state); 348 clear_selection(state); 349 return 0; 350 } 351 352 if (state->coord.col > 0) 353 { 354 ssize_t i = state->coord.col; 355 while (i <= buffer_line->length) 356 { 357 buffer_line->text[i - 1] = buffer_line->text[i]; 358 i++; 359 } 360 buffer_line->text[i - 1] = 0; 361 buffer_line->length--; 362 position_to(state, state->coord.row, state->coord.col - 1); 363 state->dirty = 1; 364 } 365 else if (state->coord.row > 0) 366 { 367 move_up(state); 368 move_end(state); 369 erase_forward(state); 370 } 371 return 0; 372 } 373 374 static int 375 erase_forward(struct DrawState* state) 376 { 377 struct BufferLine* buffer_line; 378 379 assert(state != NULL); 380 buffer_line = &state->buffer[state->coord.row]; 381 382 if (SEL_NOT_EMPTY(state)) 383 { 384 erase_selection(state); 385 position_to_selection_start(state); 386 clear_selection(state); 387 return 0; 388 } 389 390 if (buffer_line->text[state->coord.col]) 391 { 392 ssize_t i = state->coord.col; 393 while (i + 2 < buffer_line->size && buffer_line->text[i + 1]) 394 { 395 buffer_line->text[i] = buffer_line->text[i + 1]; 396 i++; 397 } 398 buffer_line->text[i] = 0; 399 buffer_line->length--; 400 state->dirty = 1; 401 } 402 else if ((state->coord.row + 1) < state->rows_count) 403 simple_join_lines(state); 404 return 0; 405 } 406 407 static int 408 erase_line(struct DrawState* state) 409 { 410 struct BufferLine* current = NULL; 411 struct BufferLine* next = NULL; 412 uint32_t* ptext = NULL; 413 414 assert(state != NULL); 415 if (state->rows_count == 1) 416 { 417 *state->buffer[0].text = 0; 418 state->buffer[0].length = 0; 419 return 0; 420 } 421 for (ssize_t row = state->coord.row; row < state->rows_count - 1; row++) 422 { 423 current = state->buffer + row; 424 next = state->buffer + row + 1; 425 U32_SAFE_COPY(current->text, ptext, current->size, next->text, 426 next->size, erase_line_error); 427 current->length = next->length; 428 } 429 free_bufferline(&state->buffer[state->rows_count - 1]); 430 if ((state->coord.row + 1) == state->rows_count) 431 state->coord.row--; 432 position_to(state, state->coord.row, 0); 433 state->rows_count--; 434 state->dirty = 1; 435 recalculate_line_numbers(state); 436 return 0; 437 438 erase_line_error: 439 state->running = 0; 440 return 1; 441 } 442 443 static int 444 erase_prev_word(struct DrawState* state) 445 { 446 uint32_t* pline; 447 ssize_t len; 448 int i, j, d; 449 450 assert((state != NULL) && (state->buffer != NULL) 451 && (state->buffer[state->coord.row].text != NULL)); 452 i = state->coord.col; 453 len = state->buffer[state->coord.row].length; 454 pline = state->buffer[state->coord.row].text; 455 456 if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) 457 i--; 458 while (i > 0 && u32_is_word_boundary(pline[i], 0)) 459 i--; 460 while (i > 0 && !u32_is_word_boundary(pline[i], 0) 461 && !u32_is_word_boundary(pline[i - 1], 0)) 462 i--; 463 464 d = state->coord.col - i; 465 466 for (j = i; j + d < len; j++) 467 pline[j] = pline[j + d]; 468 469 len -= d; 470 state->buffer[state->coord.row].length = len; 471 pline[len] = 0; 472 state->coord.col = i; 473 state->display_column = display_length(pline, state->coord.col); 474 if (state->display_column < state->first_shown.col) 475 state->first_shown.col = state->display_column; 476 state->dirty = 1; 477 return 0; 478 } 479 480 static int 481 erase_selection(struct DrawState* state) 482 { 483 assert(state != NULL); 484 if (SEL_NOT_EMPTY(state)) 485 { 486 uint32_t* buf = NULL; 487 uint32_t* temp = NULL; 488 /* Selection start row/col */ 489 int ssr = 0; 490 int ssc = 0; 491 /* Selection end row/col */ 492 int ser = 0; 493 int sec = 0; 494 int ser_len = 0; 495 ssize_t bufsize; 496 497 get_selection(&ssr, &ssc, &ser, &sec, state); 498 499 assert(ser < state->buffer_size); 500 ser_len = state->buffer[ser].length; 501 bufsize = ssc + ser_len - sec + 1; 502 buf = calloc(bufsize, sizeof(uint32_t)); 503 if (!buf) 504 exit(print_error(ENOMEM, "Memory allocation failed")); 505 506 U32_MEMCCPY(buf, state->buffer[ser].text + sec, bufsize, temp); 507 ENSURE_SIZE(state->buffer[ssr].text, temp, 508 state->buffer[ssr].size, bufsize, bufsize, 509 erase_selection_alloc_error, uint32_t); 510 U32_MEMCCPY((state->buffer[ssr].text + ssc), buf, bufsize, temp); 511 512 state->buffer[ssr].size = MAX(bufsize, state->buffer[ssr].size); 513 state->buffer[ssr].length = ssc + ser_len - sec; 514 515 if (ssr == ser) 516 goto erase_selection_same_line; 517 for (ssize_t row = 0; ser + row + 1 < state->rows_count; row++) 518 { 519 ENSURE_SIZE(state->buffer[ssr + row + 1].text, temp, 520 state->buffer[ssr + row + 1].size, 521 state->buffer[ser + row + 1].size, 522 state->buffer[ser + row + 1].size, 523 erase_selection_alloc_error, uint32_t); 524 U32_MEMCCPY(state->buffer[ssr + row + 1].text, 525 state->buffer[ser + row + 1].text, 526 state->buffer[ser + row + 1].size, temp); 527 state->buffer[ssr + row + 1].size 528 = MAX(state->buffer[ssr + row + 1].size, 529 state->buffer[ser + row + 1].size); 530 state->buffer[ssr + row + 1].length 531 = state->buffer[ser + row + 1].length; 532 } 533 for (ssize_t row = state->rows_count - (ser - ssr); 534 row < state->rows_count; row++) 535 { 536 *state->buffer[row].text = 0; 537 state->buffer[row].length = 0; 538 } 539 state->rows_count -= ser - ssr; 540 recalculate_line_numbers(state); 541 542 erase_selection_same_line: 543 free(buf); 544 state->dirty = 1; 545 } 546 return 0; 547 548 erase_selection_alloc_error: 549 state->running = 0; 550 return 1; 551 } 552 553 static int 554 erase_to_end(struct DrawState* state) 555 { 556 struct BufferLine* buffer_line; 557 558 assert(state != NULL); 559 buffer_line = &state->buffer[state->coord.row]; 560 assert((buffer_line != NULL) && (buffer_line->text != NULL)); 561 562 if (SEL_NOT_EMPTY(state)) 563 { 564 erase_selection(state); 565 position_to_selection_start(state); 566 clear_selection(state); 567 return 0; 568 } 569 570 buffer_line->text[state->coord.col] = 0; 571 buffer_line->length = state->coord.col; 572 move_end(state); 573 state->dirty = 1; 574 return 0; 575 } 576 577 static int 578 erase_to_start(struct DrawState* state) 579 { 580 struct BufferLine* buffer_line; 581 ssize_t i; 582 ssize_t len; 583 584 assert(state != NULL); 585 i = state->coord.col; 586 buffer_line = &state->buffer[state->coord.row]; 587 assert((buffer_line != NULL) && (buffer_line->text != NULL)); 588 len = buffer_line->length; 589 590 if (SEL_NOT_EMPTY(state)) 591 { 592 erase_selection(state); 593 position_to_selection_start(state); 594 clear_selection(state); 595 return 0; 596 } 597 598 do 599 { 600 assert(i < buffer_line->length); 601 buffer_line->text[i - state->coord.col] = buffer_line->text[i]; 602 i++; 603 } while (buffer_line->text[i]); 604 buffer_line->text[len - state->coord.col] = 0; 605 buffer_line->length = len - state->coord.col; 606 move_start(state); 607 state->dirty = 1; 608 return 0; 609 } 610 611 static int 612 exit_program(struct DrawState* state) 613 { 614 char* temp = NULL; 615 616 assert(state != NULL); 617 if (state->dirty) 618 { 619 SET(state->mode, M_PROMPT); 620 MEMCCPY(state->prompt, prompt_dirty, LINE_DEFAULT, temp); 621 state->prompt_callback = quit_callback; 622 } 623 else 624 state->running = 0; 625 state->error = ERR_NONE; 626 return 0; 627 } 628 629 void 630 get_selection(int* ssr, int* ssc, int* ser, int* sec, 631 const struct DrawState* state) 632 { 633 assert(state != NULL); 634 if (state->sel_start.row == state->sel_end.row) 635 { 636 if (state->sel_start.col <= state->sel_end.col) 637 { 638 if (ssc) 639 *ssc = state->sel_start.col; 640 if (sec) 641 *sec = state->sel_end.col; 642 } 643 else /* Swap */ 644 { 645 if (ssc) 646 *ssc = state->sel_end.col; 647 if (sec) 648 *sec = state->sel_start.col; 649 } 650 651 if (ssr) 652 *ssr = state->sel_start.row; 653 if (ser) 654 *ser = state->sel_end.row; 655 } 656 else if (state->sel_start.row < state->sel_end.row) 657 { 658 if (ssr) 659 *ssr = state->sel_start.row; 660 if (ser) 661 *ser = state->sel_end.row; 662 if (ssc) 663 *ssc = state->sel_start.col; 664 if (sec) 665 *sec = state->sel_end.col; 666 } 667 else /* Swap */ 668 { 669 if (ssr) 670 *ssr = state->sel_end.row; 671 if (ser) 672 *ser = state->sel_start.row; 673 if (ssc) 674 *ssc = state->sel_end.col; 675 if (sec) 676 *sec = state->sel_start.col; 677 } 678 } 679 680 static int 681 go_to_line_callback(struct DrawState* state) 682 { 683 ssize_t lineno = 0; 684 ssize_t len = 0; 685 char* u8_input = NULL; 686 687 assert(state != NULL); 688 if (*state->input) 689 { 690 len = u32_strlen(state->input); 691 u8_input = calloc(len + 1, UTF8REPMAX); 692 if (!u8_input) 693 { 694 state->running = 0; 695 return 1; 696 } 697 unicode_string_to_u8(u8_input, state->input, len + 1); 698 errno = 0; 699 lineno = strtoul(u8_input, NULL, 10); 700 if (errno) 701 state->error = ERR_INVALID_NUM; 702 if (!state->error) 703 { 704 if (lineno > state->rows_count - 1) 705 lineno = state->rows_count; 706 else if (lineno < 1) 707 lineno = 1; 708 position_to(state, lineno - 1, state->coord.col); 709 *state->input = 0; 710 } 711 free(u8_input); 712 } 713 if (!state->error) 714 state->input_callback = NULL; 715 return 0; 716 } 717 718 static void 719 handle_key_event(struct tb_event* ev, struct DrawState* state) 720 { 721 const struct Key* current = NULL; 722 723 assert((state != NULL) && (ev != NULL)); 724 state->error = ERR_NONE; 725 errno = 0; 726 727 if (IN(state->mode, M_PROMPT)) 728 { 729 if (ev->ch && strchr("YyNn", ev->ch)) 730 { 731 if (state->prompt_callback) 732 state->prompt_callback(state, ev); 733 CLEAR(state->mode, M_PROMPT); 734 *state->prompt = 0; 735 } 736 else if (ev->key == TB_KEY_ESC) 737 cancel_close(state); 738 return; 739 } 740 741 /* Remember to also edit the following condition if you 742 * reassign motion keys */ 743 if (ev->key != TB_KEY_ARROW_DOWN && ev->key != TB_KEY_ARROW_UP 744 && ev->key != TB_KEY_PGDN && ev->key != TB_KEY_PGUP 745 && ev->key != TB_KEY_CTRL_B && ev->key != TB_KEY_CTRL_F 746 && !(IN(state->mode, M_COMMAND) && strchr("jkgGHLM", ev->ch))) 747 state->saved_column = -1; 748 749 if (IN(state->mode, M_INPUT)) 750 { 751 for (size_t key = 0; key < LEN(input_dialog_keys); key++) 752 { 753 current = input_dialog_keys + key; 754 if (((current->ev.mod == ev->mod) 755 && (current->ev.key == ev->key) && !ev->ch) 756 || (current->ev.ch 757 && (current->ev.ch == ev->ch))) 758 if (current->callback) 759 { 760 current->callback(state); 761 return; 762 } 763 } 764 if (ev->ch) 765 { 766 if ((IN(state->mode, M_INPUT_NUM) 767 && strchr("0123456789", (const char)ev->ch)) 768 || !IN(state->mode, M_INPUT_NUM)) 769 input_insert_char(state, ev->ch); 770 else 771 state->error = ERR_INPUT_NONNUM; 772 return; 773 } 774 } 775 776 for (size_t key = 0; key < LEN(top_level_keys); key++) 777 { 778 current = &top_level_keys[key]; 779 if (((current->ev.mod == ev->mod) 780 && (current->ev.key == ev->key) && !ev->ch) 781 || (current->ev.ch && (current->ev.ch == ev->ch))) 782 if (current->callback) 783 { 784 current->callback(state); 785 return; 786 } 787 } 788 789 if (IN(state->mode, M_HELP)) 790 { 791 state->error = ERR_DLG_OPEN; 792 return; 793 } 794 795 for (size_t key = 0; key < LEN(global_keys); key++) 796 { 797 current = &global_keys[key]; 798 assert(current != NULL); 799 if (((current->ev.mod == ev->mod) 800 && (current->ev.key == ev->key) && !ev->ch) 801 || (current->ev.ch && (current->ev.ch == ev->ch))) 802 if (current->callback) 803 { 804 current->callback(state); 805 return; 806 } 807 } 808 809 if (IN(state->mode, M_COMMAND)) 810 { 811 for (size_t key = 0; key < LEN(command_keys); key++) 812 { 813 current = &command_keys[key]; 814 assert(current != NULL); 815 if (((current->ev.mod == ev->mod) 816 && (current->ev.key == ev->key) && !ev->ch) 817 || (current->ev.ch 818 && (current->ev.ch == ev->ch))) 819 if (current->callback) 820 { 821 current->callback(state); 822 return; 823 } 824 } 825 } 826 else 827 { 828 for (size_t key = 0; key < LEN(insert_keys); key++) 829 { 830 current = &insert_keys[key]; 831 if (((current->ev.mod == ev->mod) 832 && (current->ev.key == ev->key) && !ev->ch) 833 || (current->ev.ch 834 && (current->ev.ch == ev->ch))) 835 if (current->callback) 836 { 837 current->callback(state); 838 return; 839 } 840 } 841 if (ev->ch) 842 { 843 insert_char(state, ev->ch); 844 return; 845 } 846 } 847 848 state->error = ERR_UNKNOWN_KEY; 849 } 850 851 static int 852 input_cancel(struct DrawState* state) 853 { 854 assert(state != NULL); 855 if (state->error) 856 { 857 state->error = ERR_NONE; 858 return 0; 859 } 860 *state->input = 0; 861 if (state->input_update_callback) 862 state->input_update_callback(state); 863 CLEAR(state->mode, M_INPUT | M_INPUT_NUM); 864 state->input_callback = NULL; 865 state->input_update_callback = NULL; 866 state->chain_dialog = 0; 867 return 0; 868 } 869 870 static int 871 input_enter(struct DrawState* state) 872 { 873 int hide_dialog = 1; 874 875 assert(state != NULL); 876 if (state->input_callback) 877 { 878 state->input_callback(state); 879 hide_dialog = (!state->search || !*state->search 880 || !state->chain_dialog || *state->input != 0); 881 if (hide_dialog) 882 { 883 if (!state->error) 884 CLEAR(state->mode, M_INPUT | M_INPUT_NUM); 885 state->input_callback = NULL; 886 } 887 } 888 else if (hide_dialog) 889 CLEAR(state->mode, M_INPUT | M_INPUT_NUM); 890 if (hide_dialog) 891 state->input_update_callback = NULL; 892 return 0; 893 } 894 895 static int 896 input_erase_backwards(struct DrawState* state) 897 { 898 assert(state != NULL); 899 if (state->input_column > 0) 900 { 901 ssize_t i = state->input_column; 902 do 903 { 904 state->input[i - 1] = state->input[i]; 905 i++; 906 } while (state->input[i]); 907 state->input[i - 1] = 0; 908 input_position_to(state, state->input_column - 1); 909 if (state->input_update_callback) 910 state->input_update_callback(state); 911 } 912 return 0; 913 } 914 915 static int 916 input_erase_forward(struct DrawState* state) 917 { 918 assert(state != NULL); 919 if (state->input) 920 { 921 ssize_t i = state->input_column; 922 while (i + 2 < MAXINPUT && state->input[i + 1]) 923 { 924 state->input[i] = state->input[i + 1]; 925 i++; 926 } 927 state->input[i] = 0; 928 if (state->input_update_callback) 929 state->input_update_callback(state); 930 } 931 return 0; 932 } 933 934 static int 935 input_erase_to_end(struct DrawState* state) 936 { 937 uint32_t* temp = NULL; 938 assert(state != NULL); 939 state->input[state->input_column] = 0; 940 input_move_end(state); 941 U32_MEMCCPY(state->search, state->input, MAXINPUT, temp); 942 if (state->input_update_callback) 943 state->input_update_callback(state); 944 return 0; 945 } 946 947 static int 948 input_erase_to_start(struct DrawState* state) 949 { 950 ssize_t i; 951 ssize_t len; 952 953 assert(state != NULL); 954 i = state->input_column; 955 len = u32_strlen(state->input); 956 957 do 958 { 959 assert(i < len); 960 state->input[i - state->input_column] = state->input[i]; 961 i++; 962 } while (state->input[i]); 963 state->input[len - state->input_column] = 0; 964 input_move_start(state); 965 if (state->input_update_callback) 966 state->input_update_callback(state); 967 return 0; 968 } 969 970 static int 971 input_insert_char(struct DrawState* state, uint32_t ch) 972 { 973 ssize_t len; 974 assert(state != NULL); 975 len = u32_strlen(state->input); 976 if (state->input_column + 1 < MAXINPUT) 977 { 978 ssize_t i = len + 1; 979 int add_null = state->input[state->input_column] == 0; 980 while (i + 2 < MAXINPUT && i > state->input_column) 981 { 982 state->input[i] = state->input[i - 1]; 983 i--; 984 } 985 state->input[state->input_column] = ch; 986 input_position_to(state, state->input_column + 1); 987 if (add_null) 988 state->input[state->input_column] = 0; 989 if (state->input_update_callback) 990 state->input_update_callback(state); 991 } 992 return 0; 993 } 994 995 static int 996 input_insert_space(struct DrawState* state) 997 { 998 return input_insert_char(state, ' '); 999 } 1000 1001 static int 1002 input_insert_tab(struct DrawState* state) 1003 { 1004 return input_insert_char(state, '\t'); 1005 } 1006 1007 static int 1008 input_move_end(struct DrawState* state) 1009 { 1010 assert(state != NULL); 1011 input_position_to(state, u32_strlen(state->input)); 1012 return 0; 1013 } 1014 1015 static int 1016 input_move_left(struct DrawState* state) 1017 { 1018 assert(state != NULL); 1019 if (state->input_column == 0) 1020 return 0; 1021 input_position_to(state, state->input_column - 1); 1022 return 0; 1023 } 1024 1025 static int 1026 input_move_next_word(struct DrawState* state) 1027 { 1028 uint32_t* pline; 1029 ssize_t len; 1030 int i; 1031 1032 assert((state != NULL) && (state->input != NULL)); 1033 i = state->input_column; 1034 pline = state->input; 1035 len = u32_strlen(state->input); 1036 while (i < len && !u32_is_word_boundary(pline[i], 0)) 1037 i++; 1038 while (i < len && u32_is_word_boundary(pline[i], 0)) 1039 i++; 1040 input_position_to(state, i); 1041 return 0; 1042 } 1043 1044 static int 1045 input_move_prev_word(struct DrawState* state) 1046 { 1047 int i; 1048 uint32_t* pline; 1049 1050 assert((state != NULL) && (state->input != NULL)); 1051 i = state->input_column; 1052 pline = state->input; 1053 assert(pline != NULL); 1054 if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) 1055 i--; 1056 while (i > 0 && u32_is_word_boundary(pline[i], 0)) 1057 i--; 1058 while (i > 0 && !u32_is_word_boundary(pline[i], 0) 1059 && !u32_is_word_boundary(pline[i - 1], 0)) 1060 i--; 1061 input_position_to(state, i); 1062 return 0; 1063 } 1064 1065 static int 1066 input_move_right(struct DrawState* state) 1067 { 1068 assert(state != NULL); 1069 input_position_to(state, state->input_column + 1); 1070 return 0; 1071 } 1072 1073 static int 1074 input_move_start(struct DrawState* state) 1075 { 1076 input_position_to(state, 0); 1077 return 0; 1078 } 1079 1080 static void 1081 input_position_to(struct DrawState* state, const int column) 1082 { 1083 ssize_t len; 1084 1085 assert((state != NULL) && (state->input != NULL)); 1086 len = u32_strlen(state->input); 1087 state->input_column = column; 1088 1089 if (state->input_column > len) 1090 state->input_column = len; 1091 state->input_display_column 1092 = display_length(state->input, state->input_column); 1093 if (state->input_first_shown_column > state->input_display_column) 1094 state->input_first_shown_column = state->input_display_column; 1095 if (state->input_first_shown_column + (state->input_dialog_width - 4) 1096 < state->input_column) 1097 state->input_first_shown_column = state->input_display_column 1098 - (state->input_dialog_width - 4); 1099 } 1100 1101 static int 1102 insert_char(struct DrawState* state, uint32_t ch) 1103 { 1104 struct BufferLine* buffer_line = NULL; 1105 uint32_t* ptext = NULL; 1106 ssize_t i, len, size; 1107 1108 assert((state != NULL) && (state->buffer != NULL)); 1109 ENSURE_SIZE(state->buffer[state->coord.row].text, ptext, 1110 state->buffer[state->coord.row].size, 1111 state->buffer[state->coord.row].length + 2, 1112 state->buffer[state->coord.row].size + LINE_ALLOC_DELTA, 1113 insert_char_error, uint32_t); 1114 1115 if (SEL_NOT_EMPTY(state)) 1116 { 1117 erase_selection(state); 1118 position_to_selection_start(state); 1119 clear_selection(state); 1120 } 1121 1122 buffer_line = &state->buffer[state->coord.row]; 1123 len = buffer_line->length; 1124 size = buffer_line->size; 1125 i = len; 1126 1127 assert(buffer_line->text != NULL); 1128 if (!IN(state->mode, M_OVERWRITE)) 1129 while (i + 1 < size && i > state->coord.col) 1130 { 1131 buffer_line->text[i] = buffer_line->text[i - 1]; 1132 i--; 1133 } 1134 buffer_line->text[state->coord.col] = ch; 1135 if (state->coord.col == len || !IN(state->mode, M_OVERWRITE)) 1136 buffer_line->length++; 1137 position_to(state, state->coord.row, state->coord.col + 1); 1138 buffer_line->text[buffer_line->length] = 0; 1139 1140 if (!enable_wrap) 1141 goto insert_skip_wrap; 1142 1143 while (state->display_column >= wrap_width) 1144 { 1145 int saved_column = state->coord.col; 1146 buffer_line = state->buffer + state->coord.row; 1147 assert((buffer_line != NULL) && (buffer_line->text != NULL)); 1148 state->coord.col = buffer_length(buffer_line->text, wrap_width); 1149 /* clang-format off */ 1150 while (state->coord.col > 0 1151 && !u32_is_word_boundary( 1152 buffer_line->text[state->coord.col - 1], 1)) 1153 state->coord.col--; 1154 /* clang-format on */ 1155 if (state->coord.col == 0) 1156 { 1157 state->coord.col = saved_column; 1158 break; 1159 } 1160 simple_insert_line(state); 1161 state->saved_column = -1; 1162 position_to(state, state->coord.row + 1, 1163 saved_column - state->coord.col); 1164 } 1165 1166 insert_skip_wrap: 1167 state->dirty = 1; 1168 1169 return 0; 1170 1171 insert_char_error: 1172 state->running = 0; 1173 return 1; 1174 } 1175 1176 static int 1177 insert_line(struct DrawState* state) 1178 { 1179 struct BufferLine* buffer_line = NULL; 1180 1181 assert(state != NULL); 1182 if (SEL_NOT_EMPTY(state)) 1183 { 1184 erase_selection(state); 1185 position_to_selection_start(state); 1186 clear_selection(state); 1187 } 1188 1189 if (!enable_wrap) 1190 goto insert_skip_wrap; 1191 1192 while (state->display_column >= wrap_width) 1193 { 1194 int saved_column = state->coord.col; 1195 buffer_line = state->buffer + state->coord.row; 1196 assert((buffer_line != NULL) && (buffer_line->text != NULL)); 1197 state->coord.col = buffer_length(buffer_line->text, wrap_width); 1198 /* clang-format off */ 1199 while (state->coord.col > 0 1200 && !u32_is_word_boundary( 1201 buffer_line->text[state->coord.col - 1], 1)) 1202 state->coord.col--; 1203 /* clang-format on */ 1204 if (state->coord.col == 0) 1205 { 1206 state->coord.col = saved_column; 1207 break; 1208 } 1209 simple_insert_line(state); 1210 state->saved_column = -1; 1211 position_to(state, state->coord.row + 1, 1212 saved_column - state->coord.col); 1213 } 1214 1215 insert_skip_wrap: 1216 simple_insert_line(state); 1217 state->saved_column = -1; 1218 position_to(state, state->coord.row + 1, 0); 1219 state->dirty = 1; 1220 return 0; 1221 } 1222 1223 static int 1224 insert_space(struct DrawState* state) 1225 { 1226 return insert_char(state, ' '); 1227 } 1228 1229 static int 1230 insert_tab(struct DrawState* state) 1231 { 1232 return insert_char(state, '\t'); 1233 } 1234 1235 static int 1236 join_lines(struct DrawState* state) 1237 { 1238 struct BufferLine* current = NULL; 1239 struct BufferLine* next = NULL; 1240 uint32_t* ptext = NULL; 1241 /* Selection start row/col */ 1242 int ssr = 0; 1243 int ssc = 0; 1244 /* Selection end row */ 1245 int ser = 0; 1246 int selection = 0; 1247 int saved_row; 1248 1249 assert(state != NULL); 1250 saved_row = state->coord.row; 1251 if (SEL_NOT_EMPTY(state)) 1252 { 1253 get_selection(&ssr, &ssc, &ser, NULL, state); 1254 ser--; 1255 selection = 1; 1256 clear_selection(state); 1257 } 1258 else 1259 { 1260 ssr = state->coord.row; 1261 ssc = state->coord.col; 1262 ser = state->coord.row; 1263 } 1264 1265 if ((ssr + 1) == state->rows_count) 1266 return 0; 1267 1268 for (int row = ssr; row <= ser; row++) 1269 { 1270 assert(ssr + 1 < state->buffer_size); 1271 current = state->buffer + ssr; 1272 next = state->buffer + ssr + 1; 1273 assert((current != NULL) && (next != NULL)); 1274 ENSURE_SIZE(current->text, ptext, current->size, 1275 current->length + 3, current->length + LINE_ALLOC_DELTA, 1276 join_lines_error, uint32_t); 1277 state->coord.row = ssr; 1278 if (current->length > 0 1279 && !u32_is_word_boundary(current->text[current->length 1280 - 1], 1281 1) 1282 && !u32_is_word_boundary(next->text[0], 1)) 1283 { 1284 current->text[current->length] = ' '; 1285 current->text[current->length + 1] = 0; 1286 current->length++; 1287 state->coord.col = current->length - 1; 1288 } 1289 else 1290 state->coord.col = current->length; 1291 state->display_column 1292 = display_length(current->text, state->coord.col); 1293 simple_join_lines(state); 1294 } 1295 1296 state->coord.row = saved_row; 1297 if (selection) 1298 position_to(state, ssr, ssc); 1299 1300 return 0; 1301 1302 join_lines_error: 1303 state->running = 0; 1304 return 1; 1305 } 1306 1307 static int 1308 load_file(struct DrawState* state) 1309 { 1310 struct BufferLine* newchunk = NULL; 1311 struct BufferLine* line = NULL; 1312 FILE* input = NULL; 1313 uint32_t* newtext = NULL; 1314 uint32_t* ptext = NULL; 1315 char* input_line = NULL; 1316 ssize_t input_size = limits[L_LINE_MAX]; 1317 ssize_t input_len = 0; 1318 int possible_nulbyte; 1319 1320 assert(state != NULL); 1321 input_line = calloc(input_size, 1); 1322 if (!input_line) 1323 goto load_file_alloc_error; 1324 newchunk = calloc(ALLOC_DELTA, sizeof(struct BufferLine)); 1325 if (!newchunk) 1326 goto load_file_alloc_error; 1327 for (ssize_t row = 0; row < ALLOC_DELTA; row++) 1328 { 1329 init_bufferline(newchunk + row); 1330 newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); 1331 if (!newtext) 1332 goto load_file_alloc_error; 1333 newchunk[row].size = LINE_DEFAULT; 1334 newchunk[row].text = newtext; 1335 } 1336 1337 state->buffer = newchunk; 1338 state->buffer_size = ALLOC_DELTA; 1339 state->rows_count = 0; 1340 1341 input = fopen(state->filename, "rt"); 1342 if (!input) 1343 return 1; 1344 1345 state->action = ACT_READING; 1346 redraw_status(state); 1347 1348 possible_nulbyte = 0; 1349 while (!feof(input)) 1350 { 1351 char* eol = NULL; 1352 char* nulbyte = NULL; 1353 1354 if (!fgets(input_line, input_size, input)) 1355 continue; 1356 1357 if (possible_nulbyte) 1358 { 1359 /* Definitely not EOF, NUL byte confirmed */ 1360 state->error = ERR_BINARY; 1361 fclose(input); 1362 free(input_line); 1363 return 1; 1364 } 1365 1366 eol = strchr(input_line, '\n'); 1367 if (eol) 1368 *eol = 0; 1369 nulbyte = strchr(input_line, 0); 1370 1371 /* We don't know if the NUL byte not being equal to 1372 * (possibly NULL) eol is the result of EOF or not 1373 */ 1374 if (!open_binary && nulbyte && (nulbyte != eol)) 1375 possible_nulbyte = 1; 1376 1377 if (state->rows_count + 1 == state->buffer_size) 1378 { 1379 state->buffer_size += ALLOC_DELTA; 1380 newchunk = realloc(state->buffer, 1381 state->buffer_size * sizeof(struct BufferLine)); 1382 if (!newchunk) 1383 goto load_file_alloc_error; 1384 state->buffer = newchunk; 1385 for (ssize_t row = state->buffer_size - ALLOC_DELTA; 1386 row < state->buffer_size; row++) 1387 { 1388 init_bufferline(state->buffer + row); 1389 newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); 1390 if (!newtext) 1391 goto load_file_alloc_error; 1392 state->buffer[row].size = LINE_DEFAULT; 1393 state->buffer[row].text = newtext; 1394 } 1395 } 1396 line = state->buffer + state->rows_count; 1397 input_len = strlen(input_line); 1398 ENSURE_SIZE(line->text, ptext, line->size, input_len + 1, 1399 input_len + LINE_ALLOC_DELTA, load_file_alloc_error, 1400 uint32_t); 1401 1402 U32_U8_MEMCCPY(line->text, input_line, line->size, ptext); 1403 line->length = u32_strlen(line->text); 1404 state->rows_count++; 1405 } 1406 fclose(input); 1407 state->action = ACT_IDLE; 1408 1409 if (state->rows_count == 0) 1410 state->rows_count++; 1411 1412 free(input_line); 1413 return 0; 1414 1415 load_file_alloc_error: 1416 return 1; 1417 } 1418 1419 static int 1420 move_bottom(struct DrawState* state) 1421 { 1422 position_to(state, state->rows_count > 1 ? state->rows_count - 1 : 0, 1423 state->coord.col); 1424 return 0; 1425 } 1426 1427 static int 1428 move_down(struct DrawState* state) 1429 { 1430 position_to(state, state->coord.row + 1, state->coord.col); 1431 return 0; 1432 } 1433 1434 static int 1435 move_end(struct DrawState* state) 1436 { 1437 position_to(state, state->coord.row, 1438 state->buffer[state->coord.row].length); 1439 return 0; 1440 } 1441 1442 static int 1443 move_left(struct DrawState* state) 1444 { 1445 position_to(state, state->coord.row, state->coord.col - 1); 1446 return 0; 1447 } 1448 1449 static int 1450 move_next_word(struct DrawState* state) 1451 { 1452 uint32_t* pline; 1453 ssize_t len; 1454 int i; 1455 1456 assert((state != NULL) && (state->buffer != NULL)); 1457 i = state->coord.col; 1458 pline = state->buffer[state->coord.row].text; 1459 len = state->buffer[state->coord.row].length; 1460 while (i < len && !u32_is_word_boundary(pline[i], 0)) 1461 i++; 1462 while (i < len && u32_is_word_boundary(pline[i], 0)) 1463 i++; 1464 position_to(state, state->coord.row, i); 1465 return 0; 1466 } 1467 1468 static int 1469 move_page_down(struct DrawState* state) 1470 { 1471 assert(state != NULL); 1472 if ((state->coord.row + state->maxy) < state->rows_count + 2) 1473 position_to(state, state->coord.row + state->maxy - 2, 1474 state->coord.col); 1475 else 1476 move_bottom(state); 1477 return 0; 1478 } 1479 1480 static int 1481 move_page_up(struct DrawState* state) 1482 { 1483 assert(state != NULL); 1484 if (state->coord.row > state->maxy - 2) 1485 position_to(state, state->coord.row - (state->maxy - 2), 1486 state->coord.col); 1487 else 1488 move_top(state); 1489 return 0; 1490 } 1491 1492 static int 1493 move_prev_word(struct DrawState* state) 1494 { 1495 uint32_t* pline; 1496 int i; 1497 1498 assert((state != NULL) && (state->buffer != NULL)); 1499 i = state->coord.col; 1500 pline = state->buffer[state->coord.row].text; 1501 assert(pline != NULL); 1502 if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) 1503 i--; 1504 while (i > 0 && u32_is_word_boundary(pline[i], 0)) 1505 i--; 1506 while (i > 0 && !u32_is_word_boundary(pline[i], 0) 1507 && !u32_is_word_boundary(pline[i - 1], 0)) 1508 i--; 1509 position_to(state, state->coord.row, i); 1510 return 0; 1511 } 1512 1513 static int 1514 move_right(struct DrawState* state) 1515 { 1516 position_to(state, state->coord.row, state->coord.col + 1); 1517 return 0; 1518 } 1519 1520 static int 1521 move_screen_bottom(struct DrawState* state) 1522 { 1523 assert(state != NULL); 1524 if (state->rows_count + 2 < state->maxy) 1525 position_to(state, state->rows_count - 1, state->coord.col); 1526 else 1527 { 1528 if ((state->first_shown.row + state->maxy) 1529 < state->rows_count + 2) 1530 position_to(state, 1531 state->first_shown.row + state->maxy - 2, 1532 state->coord.col); 1533 } 1534 return 0; 1535 } 1536 1537 static int 1538 move_screen_middle(struct DrawState* state) 1539 { 1540 assert(state != NULL); 1541 if (state->rows_count + 2 < state->maxy) 1542 position_to(state, state->rows_count / 2, state->coord.col); 1543 else 1544 { 1545 int row = state->first_shown.row + (state->maxy - 2) / 2; 1546 if (state->rows_count < (row + (state->maxy - 2) / 2)) 1547 row = state->rows_count - (state->maxy - 2) / 2; 1548 position_to(state, row, state->coord.col); 1549 } 1550 return 0; 1551 } 1552 1553 static int 1554 move_screen_top(struct DrawState* state) 1555 { 1556 assert(state != NULL); 1557 position_to(state, state->first_shown.row, state->coord.col); 1558 return 0; 1559 } 1560 1561 static int 1562 move_start(struct DrawState* state) 1563 { 1564 assert(state != NULL); 1565 position_to(state, state->coord.row, 0); 1566 return 0; 1567 } 1568 1569 static int 1570 move_top(struct DrawState* state) 1571 { 1572 assert(state != NULL); 1573 position_to(state, 0, state->coord.col); 1574 return 0; 1575 } 1576 1577 static int 1578 move_up(struct DrawState* state) 1579 { 1580 assert(state != NULL); 1581 position_to(state, state->coord.row > 1 ? state->coord.row - 1 : 0, 1582 state->coord.col); 1583 return 0; 1584 } 1585 1586 static int 1587 next_match(struct DrawState* state) 1588 { 1589 struct BufferLine* current = NULL; 1590 const uint32_t* found = NULL; 1591 ssize_t idelta; 1592 1593 assert(state != NULL); 1594 if (!state->search || !*state->search) 1595 return 0; 1596 1597 state->error = ERR_NONE; 1598 for (ssize_t row = state->coord.row; row < state->rows_count; row++) 1599 { 1600 current = state->buffer + row; 1601 found = current->text; 1602 if (row == state->coord.row) 1603 found += state->coord.col + 1; 1604 found = u32_strstr(found, state->search); 1605 if (found) 1606 { 1607 idelta = found - (const uint32_t*)current->text; 1608 state->saved_column 1609 = display_length(current->text, idelta); 1610 if (*current->text) 1611 { 1612 position_to(state, row, idelta); 1613 return 0; 1614 } 1615 } 1616 } 1617 return 0; 1618 } 1619 1620 static int 1621 paste_from_paste(struct DrawState* state) 1622 { 1623 struct BufferLine* line = NULL; 1624 struct BufferLine* newbuf = NULL; 1625 struct Coord newpos; 1626 uint32_t* newtext = NULL; 1627 uint32_t* ptext = NULL; 1628 uint32_t* rest = NULL; 1629 ssize_t newsize = 0; 1630 ssize_t rest_size = 0; 1631 1632 assert(state != NULL); 1633 if (!state->paste_count) 1634 return 0; 1635 1636 if (SEL_NOT_EMPTY(state)) 1637 { 1638 erase_selection(state); 1639 position_to_selection_start(state); 1640 clear_selection(state); 1641 } 1642 1643 if (state->paste_count > 1) 1644 { 1645 if (state->rows_count + state->paste_count > state->buffer_size) 1646 { 1647 ssize_t old_size = state->buffer_size; 1648 while (state->rows_count + state->paste_count 1649 > state->buffer_size) 1650 state->buffer_size += ALLOC_DELTA; 1651 newbuf = realloc(state->buffer, 1652 state->buffer_size * sizeof(struct BufferLine)); 1653 for (ssize_t row = old_size; row < state->buffer_size; 1654 row++) 1655 { 1656 init_bufferline(newbuf + row); 1657 newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); 1658 newbuf[row].size = LINE_DEFAULT; 1659 newbuf[row].text = newtext; 1660 } 1661 state->buffer = newbuf; 1662 } 1663 } 1664 1665 line = state->buffer + state->coord.row; 1666 newsize = line->length + state->paste[0].length + 1; 1667 newtext = calloc(newsize, sizeof(uint32_t)); 1668 if (!newtext) 1669 { 1670 state->running = 0; 1671 goto paste_from_paste_cleanup; 1672 } 1673 U32_MEMCCPY(newtext, line->text, state->coord.col + 1, ptext); 1674 U32_MEMCCPY((newtext + state->coord.col), state->paste[0].text, newsize, 1675 ptext); 1676 1677 assign_coord(&newpos, state->coord); 1678 1679 if (state->paste_count == 1) 1680 { 1681 if (ptext) 1682 U32_MEMCCPY_EXT((ptext - 1), newtext, 1683 line->text + state->coord.col, 1684 line->length - state->coord.col, newsize, 1685 ptext); 1686 U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, 1687 paste_from_paste_cleanup); 1688 line->length = u32_strlen(line->text); 1689 newpos.col += state->paste[0].length; 1690 } 1691 else 1692 { 1693 state->rows_count += state->paste_count - 1; 1694 rest_size = line->size - state->coord.col; 1695 rest = calloc(rest_size, sizeof(uint32_t)); 1696 if (!rest) 1697 { 1698 state->running = 0; 1699 goto paste_from_paste_cleanup; 1700 } 1701 U32_MEMCCPY(rest, line->text + state->coord.col, rest_size, 1702 ptext); 1703 U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, 1704 paste_from_paste_cleanup); 1705 line->length = u32_strlen(line->text); 1706 for (ssize_t row = state->rows_count - state->coord.row 1707 - (state->paste_count - 2) - 1; 1708 row > 0; row--) 1709 { 1710 struct BufferLine* copied_line 1711 = &state->buffer[state->coord.row + row - 1]; 1712 1713 line = state->buffer + state->coord.row 1714 + state->paste_count - 2 + row; 1715 U32_SAFE_COPY(line->text, ptext, line->size, 1716 copied_line->text, copied_line->size, 1717 paste_from_paste_cleanup); 1718 line->length = u32_strlen(line->text); 1719 } 1720 line = state->buffer + state->coord.row; 1721 U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, 1722 paste_from_paste_cleanup); 1723 line->length = u32_strlen(line->text); 1724 for (ssize_t row = 1; row < state->paste_count - 1; row++) 1725 { 1726 line = state->buffer + state->coord.row + row; 1727 U32_SAFE_COPY(line->text, ptext, line->size, 1728 state->paste[row].text, state->paste[row].size, 1729 paste_from_paste_cleanup); 1730 line->length = u32_strlen(line->text); 1731 } 1732 line = state->buffer + state->coord.row + state->paste_count 1733 - 1; 1734 U32_SAFE_COPY(newtext, ptext, newsize, 1735 state->paste[state->paste_count - 1].text, 1736 state->paste[state->paste_count - 1].size + rest_size, 1737 paste_from_paste_cleanup); 1738 if (ptext) 1739 U32_MEMCCPY_EXT((ptext - 1), newtext, rest, 1740 newsize - (ptext - newtext - 1), newsize, 1741 ptext); 1742 U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, 1743 paste_from_paste_cleanup); 1744 line->length = u32_strlen(line->text); 1745 newpos.col = state->paste[state->paste_count - 1].length; 1746 newpos.row += state->paste_count - 1; 1747 state->saved_column 1748 = display_length(state->paste[state->paste_count - 1] 1749 .text, 1750 newpos.col); 1751 } 1752 1753 position_to(state, newpos.row, newpos.col); 1754 state->dirty = 1; 1755 1756 paste_from_paste_cleanup: 1757 free(rest); 1758 free(newtext); 1759 return 0; 1760 } 1761 1762 /* 1763 * state->saved_column - display coordinate 1764 * row, column - buffer coordinates (not display) 1765 */ 1766 static void 1767 position_to(struct DrawState* state, const int row, const int column) 1768 { 1769 ssize_t dlen; 1770 int old_first_shown_row; 1771 int maxx; 1772 1773 assert(state != NULL); 1774 maxx = state->maxx; 1775 if (state->line_numbers) 1776 maxx -= state->line_numbers_width + 2; 1777 old_first_shown_row = state->first_shown.row; 1778 if (row != state->coord.row) 1779 { 1780 state->coord.row = row; 1781 if ((state->coord.row + 1) > state->rows_count) 1782 state->coord.row = state->rows_count - 1; 1783 if (state->first_shown.row > state->coord.row) 1784 state->first_shown.row = state->coord.row; 1785 if (state->first_shown.row + (state->maxy - 2) 1786 < state->coord.row) 1787 state->first_shown.row 1788 = state->coord.row - (state->maxy - 2); 1789 if (state->saved_column == -1) 1790 { 1791 state->saved_column = state->display_column; 1792 state->coord.col = column; 1793 } 1794 else 1795 state->coord.col 1796 = buffer_length(state->buffer[state->coord.row] 1797 .text, 1798 state->saved_column); 1799 if (state->coord.col < 0) 1800 state->coord.col = 0; 1801 else if (state->coord.col 1802 > (int)state->buffer[state->coord.row].length) 1803 state->coord.col 1804 = state->buffer[state->coord.row].length; 1805 dlen = display_length(state->buffer[state->coord.row].text, 1806 state->coord.col); 1807 if (dlen < state->saved_column) 1808 state->display_column = dlen; 1809 else 1810 state->display_column = state->saved_column; 1811 } 1812 else 1813 { 1814 state->coord.col = column; 1815 if (state->coord.col < 0) 1816 { 1817 if (state->coord.row > 0) 1818 { 1819 state->coord.row--; 1820 if (state->first_shown.row > 0 1821 && state->first_shown.row 1822 > state->coord.row) 1823 state->first_shown.row--; 1824 state->coord.col 1825 = state->buffer[state->coord.row].length; 1826 } 1827 else 1828 state->coord.col = 0; 1829 } 1830 else if (state->coord.col 1831 > state->buffer[state->coord.row].length) 1832 { 1833 if ((state->coord.row + 1) < state->rows_count) 1834 { 1835 state->coord.row++; 1836 if (state->first_shown.row + (state->maxy - 2) 1837 < state->coord.row 1838 && state->first_shown.row + 1 1839 < state->rows_count) 1840 state->first_shown.row++; 1841 state->coord.col = 0; 1842 } 1843 else 1844 state->coord.col 1845 = state->buffer[state->coord.row].length; 1846 } 1847 state->display_column 1848 = display_length(state->buffer[state->coord.row].text, 1849 state->coord.col); 1850 state->saved_column = state->display_column; 1851 } 1852 if (state->first_shown.col > state->display_column) 1853 { 1854 state->first_shown.col = state->display_column; 1855 } 1856 if (state->first_shown.col + maxx < state->display_column + 1) 1857 { 1858 int index_first; 1859 int fsc = state->first_shown.col; 1860 index_first 1861 = buffer_length(state->buffer[state->coord.row].text, 1862 fsc); 1863 while (fsc + maxx < state->display_column + 1) 1864 { 1865 index_first++; 1866 fsc = display_length(state->buffer[state->coord.row].text, 1867 index_first); 1868 } 1869 state->first_shown.col = fsc; 1870 } 1871 if (old_first_shown_row != state->first_shown.row) 1872 recalculate_line_numbers(state); 1873 1874 if (IN(state->mode, M_SELECTION)) 1875 assign_coord(&state->sel_end, state->coord); 1876 } 1877 1878 static int 1879 position_to_selection_end(struct DrawState* state) 1880 { 1881 assert(state != NULL); 1882 if (SEL_NOT_EMPTY(state)) 1883 { 1884 int ser = 0; 1885 int sec = 0; 1886 1887 get_selection(NULL, NULL, &ser, &sec, state); 1888 1889 state->saved_column 1890 = display_length(state->buffer[ser].text, sec); 1891 position_to(state, ser, sec); 1892 state->saved_column = state->display_column; 1893 } 1894 return 0; 1895 } 1896 1897 static int 1898 position_to_selection_start(struct DrawState* state) 1899 { 1900 assert(state != NULL); 1901 if (SEL_NOT_EMPTY(state)) 1902 { 1903 int ssr = 0; 1904 int ssc = 0; 1905 1906 get_selection(&ssr, &ssc, NULL, NULL, state); 1907 1908 state->saved_column 1909 = display_length(state->buffer[ssr].text, ssc); 1910 position_to(state, ssr, ssc); 1911 state->saved_column = state->display_column; 1912 } 1913 return 0; 1914 } 1915 1916 static int 1917 prev_match(struct DrawState* state) 1918 { 1919 struct BufferLine* current = NULL; 1920 const uint32_t* found = NULL; 1921 ssize_t idelta; 1922 ssize_t row; 1923 1924 assert(state != NULL); 1925 if (!state->search || !*state->search) 1926 return 0; 1927 1928 state->error = ERR_NONE; 1929 1930 row = state->coord.row; 1931 while (1) 1932 { 1933 const uint32_t* save_found; 1934 current = state->buffer + row; 1935 found = current->text; 1936 if (!found) 1937 return 0; 1938 if (row == state->coord.row) 1939 found += state->coord.col; 1940 else 1941 found += current->length; 1942 1943 prev_match_loop: 1944 save_found = found; 1945 found = u32_strrstr(current->text, state->search, found); 1946 if (found) 1947 { 1948 idelta = found - current->text; 1949 state->saved_column 1950 = display_length(current->text, idelta); 1951 if (*current->text) 1952 { 1953 position_to(state, row, idelta); 1954 return 0; 1955 } 1956 } 1957 if (save_found == current->text) 1958 goto prev_match_end; 1959 found = save_found - 1; 1960 goto prev_match_loop; 1961 prev_match_end: 1962 if (!row) 1963 break; 1964 row--; 1965 } 1966 return 0; 1967 } 1968 1969 static int 1970 print_error(const int code, const char* msg, ...) 1971 { 1972 char buf[LINE_DEFAULT]; 1973 va_list args; 1974 va_start(args, msg); 1975 vsnprintf(buf, LINE_DEFAULT, msg, args); 1976 va_end(args); 1977 fprintf(stderr, "%s: %s\n", PROGRAM_NAME, buf); 1978 return code; 1979 } 1980 1981 static int 1982 quit_callback(struct DrawState* state, struct tb_event* ev) 1983 { 1984 assert(state != NULL); 1985 if (ev->ch == 'y') 1986 if (write_file(state)) 1987 return 1; 1988 state->prompt_callback = NULL; 1989 state->running = 0; 1990 return 0; 1991 } 1992 1993 static int 1994 reread_callback(struct DrawState* state, struct tb_event* ev) 1995 { 1996 assert(state != NULL); 1997 if (strchr("Yy", ev->ch)) 1998 if (reread_file(state)) 1999 return 1; 2000 state->prompt_callback = NULL; 2001 return 0; 2002 } 2003 2004 static int 2005 reread_file(struct DrawState* state) 2006 { 2007 assert(state != NULL); 2008 free_drawstate(state); 2009 init_drawstate(state, state->filename, state->prompt, 1, 1); 2010 2011 redraw_status(state); 2012 2013 if (load_file(state)) 2014 { 2015 state->running = 0; 2016 return state->error; 2017 } 2018 2019 recalculate_line_numbers(state); 2020 return 0; 2021 } 2022 2023 static int 2024 reread_or_callback(struct DrawState* state) 2025 { 2026 char* temp = NULL; 2027 2028 assert(state != NULL); 2029 if (state->dirty) 2030 { 2031 SET(state->mode, M_PROMPT); 2032 MEMCCPY(state->prompt, prompt_reread_dirty, LINE_DEFAULT, temp); 2033 state->prompt_callback = reread_callback; 2034 } 2035 else 2036 reread_file(state); 2037 return 0; 2038 } 2039 2040 static int 2041 save_file(struct DrawState* state) 2042 { 2043 FILE* output = NULL; 2044 struct stat st; 2045 char* buf = NULL; 2046 char* pbuf = NULL; 2047 ssize_t bufsize = LINE_DEFAULT; 2048 int result = 0; 2049 int saved_errno; 2050 2051 assert(state != NULL); 2052 if (!(buf = calloc(bufsize, 1))) 2053 goto save_file_alloc_error; 2054 2055 state->action = ACT_WRITING; 2056 redraw_status(state); 2057 errno = 0; 2058 2059 if (!create_backups || state->new_file) 2060 goto save_skip_backup_copy; 2061 2062 if (!backup_fail && !state->backup_possible) 2063 goto save_skip_backup_copy; 2064 2065 if ((unlink(state->backup_filename) < 0) && (errno != ENOENT)) 2066 { 2067 state->error = ERR_ERRNO_SET; 2068 result = 1; 2069 goto save_file_cleanup; 2070 } 2071 if (cp(state->rfn_len > 0 ? state->real_filename : state->filename, 2072 state->backup_filename)) 2073 { 2074 if (backup_fail) 2075 { 2076 state->error = ERR_ERRNO_SET; 2077 result = 1; 2078 goto save_file_cleanup; 2079 } 2080 else 2081 errno = 0; /* ignore errors */ 2082 } 2083 2084 save_skip_backup_copy: 2085 if (!(output = fopen(state->rfn_len > 0 ? state->real_filename 2086 : state->filename, 2087 "w"))) 2088 { 2089 state->error = ERR_ERRNO_SET; 2090 result = 1; 2091 goto save_file_cleanup; 2092 } 2093 2094 if (!create_backups || state->new_file) 2095 goto save_skip_backup_symlink; 2096 2097 if (lstat(state->rfn_len > 0 ? state->real_filename : state->filename, 2098 &st) 2099 < 0) 2100 { 2101 state->error = ERR_ERRNO_SET; 2102 result = 1; 2103 goto save_file_cleanup; 2104 } 2105 2106 if (!backup_fail && !state->backup_possible) 2107 goto save_skip_backup_symlink; 2108 2109 if (state->rfn_len > 0) 2110 { 2111 if (unlink(state->filename) == -1) 2112 { 2113 state->error = ERR_ERRNO_SET; 2114 result = 1; 2115 goto save_file_cleanup; 2116 } 2117 if (symlink(state->orig_real_filename, state->filename) == -1) 2118 { 2119 state->error = ERR_ERRNO_SET; 2120 result = 1; 2121 goto save_file_cleanup; 2122 } 2123 } 2124 chmod(state->rfn_len > 0 ? state->real_filename : state->filename, 2125 st.st_mode); 2126 2127 save_skip_backup_symlink: 2128 if (state->rows_count == 1 && state->buffer[0].length == 0) 2129 goto save_file_cleanup; 2130 2131 for (ssize_t row = 0; row < state->rows_count; row++) 2132 { 2133 ENSURE_SIZE(buf, pbuf, bufsize, 2134 UTF8REPMAX * state->buffer[row].length + 1, 2135 UTF8REPMAX * state->buffer[row].length 2136 + LINE_ALLOC_DELTA, 2137 save_file_alloc_error, char); 2138 unicode_string_to_u8(buf, state->buffer[row].text, bufsize); 2139 fprintf(output, "%s\n", buf); 2140 } 2141 2142 state->new_file = 0; 2143 goto save_file_cleanup; 2144 2145 save_file_alloc_error: 2146 if (output) 2147 fclose(output); 2148 return 1; 2149 2150 save_file_cleanup: 2151 saved_errno = errno; 2152 if (output) 2153 fclose(output); 2154 free(buf); 2155 state->action = ACT_IDLE; 2156 errno = saved_errno; 2157 return result; 2158 } 2159 2160 static int 2161 search_dialog_callback(struct DrawState* state) 2162 { 2163 assert(state != NULL); 2164 next_match(state); 2165 if (!state->error) 2166 state->input_callback = NULL; 2167 return 0; 2168 } 2169 2170 static int 2171 search_replace_replace_dialog_callback(struct DrawState* state) 2172 { 2173 assert(state != NULL); 2174 do_search_replace(state); 2175 *state->search = 0; 2176 /*u32_strcpy(state->search, state->input); 2177 next_match(state);*/ 2178 if (!state->error) 2179 state->input_callback = NULL; 2180 state->chain_dialog = 0; 2181 return 0; 2182 } 2183 2184 static int 2185 search_replace_search_dialog_callback(struct DrawState* state) 2186 { 2187 assert(state != NULL); 2188 /*next_match(state);*/ 2189 if (!state->error) 2190 state->input_callback = NULL; 2191 *state->input = 0; 2192 show_replace(state); 2193 return 0; 2194 } 2195 2196 static int 2197 search_update_callback(struct DrawState* state) 2198 { 2199 uint32_t* temp = NULL; 2200 U32_MEMCCPY(state->search, state->input, MAXINPUT, temp); 2201 return 0; 2202 } 2203 2204 static int 2205 show_go_to_line(struct DrawState* state) 2206 { 2207 assert(state != NULL); 2208 state->input_title = (char*)go_to_dialog_title; 2209 state->input_callback = go_to_line_callback; 2210 if (!state->input) 2211 { 2212 if (!(state->input = calloc(MAXINPUT, sizeof(uint32_t)))) 2213 { 2214 state->running = 0; 2215 return 1; 2216 } 2217 } 2218 SET(state->mode, M_INPUT | M_INPUT_NUM); 2219 *state->input = 0; 2220 state->error = ERR_NONE; 2221 input_position_to(state, 0); 2222 return 0; 2223 } 2224 2225 static int 2226 show_help(struct DrawState* state) 2227 { 2228 assert(state != NULL); 2229 SET(state->mode, M_HELP); 2230 state->error = ERR_NONE; 2231 return 0; 2232 } 2233 2234 static int 2235 show_replace(struct DrawState* state) 2236 { 2237 assert(state != NULL); 2238 state->input_title = (char*)replace_dialog_title; 2239 state->input_callback = search_replace_replace_dialog_callback; 2240 state->input_update_callback = NULL; 2241 if (!state->input) 2242 { 2243 if (!(state->input = calloc(MAXINPUT, sizeof(uint32_t)))) 2244 { 2245 state->running = 0; 2246 return 1; 2247 } 2248 } 2249 *state->input = 0; 2250 SET(state->mode, M_INPUT); 2251 CLEAR(state->mode, M_INPUT_NUM); 2252 state->error = ERR_NONE; 2253 input_position_to(state, 0); 2254 return 0; 2255 } 2256 2257 static int 2258 show_search(struct DrawState* state) 2259 { 2260 assert(state != NULL); 2261 if (!state->search) 2262 { 2263 if (!(state->search = calloc(MAXINPUT, sizeof(uint32_t)))) 2264 { 2265 state->running = 0; 2266 return 1; 2267 } 2268 } 2269 *state->search = 0; 2270 state->input_title = (char*)search_dialog_title; 2271 state->input_callback = search_dialog_callback; 2272 state->input_update_callback = search_update_callback; 2273 if (!state->input) 2274 { 2275 if (!(state->input = calloc(MAXINPUT, sizeof(uint32_t)))) 2276 { 2277 state->running = 0; 2278 return 1; 2279 } 2280 } 2281 *state->input = 0; 2282 SET(state->mode, M_INPUT); 2283 CLEAR(state->mode, M_INPUT_NUM); 2284 state->error = ERR_NONE; 2285 input_position_to(state, 0); 2286 return 0; 2287 } 2288 2289 static int 2290 show_search_replace(struct DrawState* state) 2291 { 2292 assert(state != NULL); 2293 if (!state->search) 2294 { 2295 if (!(state->search = calloc(MAXINPUT, sizeof(uint32_t)))) 2296 { 2297 state->running = 0; 2298 return 1; 2299 } 2300 } 2301 *state->search = 0; 2302 state->input_title = (char*)search_dialog_title; 2303 state->input_callback = search_replace_search_dialog_callback; 2304 state->input_update_callback = search_update_callback; 2305 if (!state->input) 2306 { 2307 if (!(state->input = calloc(MAXINPUT, sizeof(uint32_t)))) 2308 { 2309 state->running = 0; 2310 return 1; 2311 } 2312 } 2313 *state->input = 0; 2314 state->chain_dialog = 1; 2315 SET(state->mode, M_INPUT); 2316 CLEAR(state->mode, M_INPUT_NUM); 2317 state->error = ERR_NONE; 2318 input_position_to(state, 0); 2319 return 0; 2320 } 2321 2322 static void 2323 simple_insert_line(struct DrawState* state) 2324 { 2325 struct BufferLine* newchunk = NULL; 2326 struct BufferLine* line = NULL; 2327 uint32_t* ptext = NULL; 2328 2329 assert(state != NULL); 2330 if (state->rows_count + 1 == state->buffer_size) 2331 { 2332 state->buffer_size += ALLOC_DELTA; 2333 newchunk = realloc(state->buffer, 2334 state->buffer_size * sizeof(struct BufferLine)); 2335 if (!newchunk) 2336 { 2337 state->running = 0; 2338 return; 2339 } 2340 state->buffer = newchunk; 2341 for (ssize_t row = state->buffer_size - ALLOC_DELTA; 2342 row < state->buffer_size; row++) 2343 { 2344 line = state->buffer + row; 2345 init_bufferline(line); 2346 ENSURE_SIZE(line->text, ptext, line->size, LINE_DEFAULT, 2347 LINE_DEFAULT, simple_insert_line_error, 2348 uint32_t); 2349 } 2350 } 2351 state->rows_count++; 2352 for (ssize_t i = state->rows_count - 1; i > (state->coord.row + 1); i--) 2353 { 2354 U32_SAFE_COPY(state->buffer[i].text, ptext, 2355 state->buffer[i].size, state->buffer[i - 1].text, 2356 state->buffer[i - 1].size, simple_insert_line_error); 2357 state->buffer[i].length = state->buffer[i - 1].length; 2358 } 2359 ENSURE_SIZE(state->buffer[state->coord.row + 1].text, ptext, 2360 state->buffer[state->coord.row + 1].size, LINE_DEFAULT, 2361 LINE_DEFAULT, simple_insert_line_error, uint32_t); 2362 *state->buffer[state->coord.row + 1].text = 0; 2363 if (state->coord.col < state->buffer[state->coord.row].length) 2364 U32_SAFE_COPY(state->buffer[state->coord.row + 1].text, ptext, 2365 state->buffer[state->coord.row + 1].size, 2366 state->buffer[state->coord.row].text + state->coord.col, 2367 state->buffer[state->coord.row].size, 2368 simple_insert_line_error); 2369 2370 state->buffer[state->coord.row + 1].length 2371 = state->buffer[state->coord.row].length - state->coord.col; 2372 2373 state->buffer[state->coord.row].text[state->coord.col] = 0; 2374 state->buffer[state->coord.row].length = state->coord.col; 2375 2376 state->dirty = 1; 2377 recalculate_line_numbers(state); 2378 return; 2379 2380 simple_insert_line_error: 2381 state->running = 0; 2382 return; 2383 } 2384 2385 static void 2386 simple_join_lines(struct DrawState* state) 2387 { 2388 struct BufferLine* first_line = NULL; 2389 struct BufferLine* next_line = NULL; 2390 uint32_t* ptext = NULL; 2391 2392 assert(state != NULL); 2393 if ((state->coord.row + 1) == state->rows_count) 2394 return; 2395 2396 first_line = &state->buffer[state->coord.row]; 2397 next_line = &state->buffer[state->coord.row + 1]; 2398 ENSURE_SIZE(first_line->text, ptext, first_line->size, 2399 first_line->length + next_line->length + 1, 2400 first_line->length + next_line->length + LINE_ALLOC_DELTA, 2401 simple_join_lines_error, uint32_t); 2402 U32_MEMCCPY((first_line->text + first_line->length), next_line->text, 2403 next_line->length + 1, ptext); 2404 first_line->length = u32_strlen(first_line->text); 2405 /* i <- i+1 */ 2406 for (ssize_t i = state->coord.row + 1; i + 1 < state->rows_count; i++) 2407 { 2408 U32_SAFE_COPY(state->buffer[i].text, ptext, 2409 state->buffer[i].size, state->buffer[i + 1].text, 2410 state->buffer[i + 1].size, simple_join_lines_error); 2411 state->buffer[i].length = state->buffer[i + 1].length; 2412 } 2413 free_bufferline(&state->buffer[state->rows_count - 1]); 2414 state->rows_count--; 2415 state->dirty = 1; 2416 recalculate_line_numbers(state); 2417 return; 2418 2419 simple_join_lines_error: 2420 state->running = 0; 2421 return; 2422 } 2423 2424 static void 2425 simple_yank(struct DrawState* state) 2426 { 2427 struct BufferLine* newbuf = NULL; 2428 uint32_t* ptext = NULL; 2429 ssize_t initialize_start; 2430 /* Selection start row/col */ 2431 int ssr = 0; 2432 int ssc = 0; 2433 /* Selection end row/col */ 2434 int ser = 0; 2435 int sec = 0; 2436 2437 assert(state != NULL); 2438 if (!SEL_NOT_EMPTY(state)) 2439 { 2440 if (state->paste_count > 0) 2441 { 2442 state->paste_count = 0; 2443 *state->paste[0].text = 0; 2444 state->paste[0].length = 0; 2445 } 2446 clear_selection(state); 2447 return; 2448 } 2449 2450 get_selection(&ssr, &ssc, &ser, &sec, state); 2451 2452 state->paste_count = ser - ssr + 1; 2453 initialize_start = state->paste_size; 2454 if (!state->paste_size) 2455 { 2456 while (state->paste_count > state->paste_size) 2457 state->paste_size += ALLOC_DELTA; 2458 newbuf = calloc(state->paste_size, sizeof(struct BufferLine)); 2459 } 2460 else if (state->paste_count > state->paste_size) 2461 { 2462 while (state->paste_count > state->paste_size) 2463 state->paste_size += ALLOC_DELTA; 2464 newbuf = realloc(state->paste, 2465 state->paste_size * sizeof(struct BufferLine)); 2466 } 2467 else 2468 newbuf = state->paste; 2469 for (ssize_t row = initialize_start; row < state->paste_size; row++) 2470 { 2471 init_bufferline(newbuf + row); 2472 ENSURE_SIZE(newbuf[row].text, ptext, newbuf[row].size, 2473 LINE_DEFAULT, LINE_DEFAULT, simple_yank_alloc_error, 2474 uint32_t); 2475 } 2476 state->paste = newbuf; 2477 if (state->paste_count == 1) 2478 { 2479 U32_SAFE_COPY(state->paste[0].text, ptext, state->paste[0].size, 2480 state->buffer[ssr].text + ssc, (sec - ssc + 1), 2481 simple_yank_alloc_error); 2482 state->paste[0].length = sec - ssc; 2483 } 2484 else 2485 { 2486 ssize_t first_line_len = state->buffer[ssr].length - ssc; 2487 2488 U32_SAFE_COPY(state->paste[0].text, ptext, state->paste[0].size, 2489 state->buffer[ssr].text + ssc, first_line_len + 1, 2490 simple_yank_alloc_error); 2491 state->paste[0].length = first_line_len; 2492 2493 for (ssize_t row = 1; row < state->paste_count - 1; row++) 2494 { 2495 U32_SAFE_COPY(state->paste[row].text, ptext, 2496 state->paste[row].size, 2497 state->buffer[ssr + row].text, 2498 state->buffer[ssr + row].size, 2499 simple_yank_alloc_error); 2500 state->paste[row].length 2501 = u32_strlen(state->paste[row].text); 2502 } 2503 2504 U32_SAFE_COPY(state->paste[state->paste_count - 1].text, ptext, 2505 state->paste[state->paste_count - 1].size, 2506 state->buffer[ser].text, (sec + 1), 2507 simple_yank_alloc_error); 2508 state->paste[state->paste_count - 1].length = sec; 2509 } 2510 return; 2511 2512 simple_yank_alloc_error: 2513 state->running = 0; 2514 return; 2515 } 2516 2517 static int 2518 suspend_program(struct DrawState* state) 2519 { 2520 tb_set_clear_attrs(TB_WHITE, TB_DEFAULT); 2521 tb_clear(); 2522 tb_set_cursor(0, 0); 2523 tb_present(); 2524 2525 kill(getpid(), SIGTSTP); 2526 2527 /* --- back from suspend --- */ 2528 2529 tb_clear(); 2530 if (draw(state)) 2531 return 1; 2532 tb_present(); 2533 return 0; 2534 } 2535 2536 static int 2537 toggle_line_numbers(struct DrawState* state) 2538 { 2539 assert(state != NULL); 2540 state->line_numbers = !state->line_numbers; 2541 position_to(state, state->coord.row, state->coord.col); 2542 return 0; 2543 } 2544 2545 static int 2546 toggle_overwrite(struct DrawState* state) 2547 { 2548 assert(state != NULL); 2549 TOGGLE(state->mode, M_OVERWRITE); 2550 CLEAR(state->mode, M_COMMAND); 2551 return 0; 2552 } 2553 2554 static int 2555 toggle_selection(struct DrawState* state) 2556 { 2557 assert(state != NULL); 2558 TOGGLE(state->mode, M_SELECTION); 2559 if (IN(state->mode, M_SELECTION)) 2560 assign_coord(&state->sel_start, state->coord); 2561 assign_coord(&state->sel_end, state->coord); 2562 return 0; 2563 } 2564 2565 /* 2566 * Not ideal (todo). In extreme cases (files generated by the testsuite, with a 2567 * huge block of full lines with no spaces), becomes unresponsive and has to be 2568 * killed. But in most "normal" cases, behaves acceptably. 2569 */ 2570 static int 2571 wrap_para(struct DrawState* state) 2572 { 2573 struct BufferLine* current = NULL; 2574 struct BufferLine* next = NULL; 2575 uint32_t* ptext = NULL; 2576 int sel; 2577 int start_row; 2578 int end_row; 2579 int cur_row; 2580 int bcol; 2581 int dlen; 2582 int col; 2583 int added_blank = 0; 2584 2585 assert(state != NULL); 2586 if (!enable_wrap) 2587 goto wrap_not_enabled; 2588 2589 sel = SEL_NOT_EMPTY(state); 2590 2591 state->action = ACT_WRAPPING; 2592 redraw_status(state); 2593 2594 if (sel) 2595 get_selection(&start_row, NULL, &end_row, NULL, state); 2596 else 2597 { 2598 start_row = state->coord.row; 2599 end_row = start_row; 2600 for (cur_row = start_row; cur_row > 0; cur_row--) 2601 if (state->buffer[cur_row - 1].length == 0 2602 || !u32_has_nonblanks( 2603 state->buffer[cur_row - 1].text)) 2604 { 2605 start_row = cur_row; 2606 break; 2607 } 2608 if (cur_row == 0) 2609 start_row = cur_row; 2610 for (cur_row = state->coord.row; 2611 cur_row + 1 < state->rows_count; cur_row++) 2612 if (state->buffer[cur_row].length == 0 2613 || !u32_has_nonblanks( 2614 state->buffer[cur_row].text)) 2615 { 2616 end_row = cur_row - 1; 2617 break; 2618 } 2619 if (cur_row + 1 == state->rows_count) 2620 end_row = cur_row - 1; 2621 } 2622 2623 cur_row = start_row; 2624 wrap_next_line: 2625 state->coord.col = 0; 2626 current = &state->buffer[cur_row]; 2627 next = &state->buffer[cur_row + 1]; 2628 state->coord.row = cur_row; 2629 2630 if (sel && (cur_row == state->sel_end.row) 2631 && (cur_row != state->sel_start.row) 2632 && (cur_row + 1 != state->rows_count)) 2633 goto wrap_finish; 2634 2635 /* If the line has less than wrap_width characters, join with next */ 2636 dlen = display_length(current->text, current->length); 2637 while ((dlen < wrap_width) && (cur_row + 1 < state->rows_count) 2638 && (current->length > 0) && u32_has_nonblanks(next->text)) 2639 { 2640 /* Insert space to avoid joining ending and beginning words */ 2641 if (current->length > 0 2642 && !u32_is_word_boundary(current->text[current->length 2643 - 1], 2644 1)) 2645 { 2646 ENSURE_SIZE(current->text, ptext, current->size, 2647 current->length + 2, current->length + 2, 2648 wrap_alloc_error, uint32_t); 2649 current->length++; 2650 current->text[current->length - 1] = (uint32_t)' '; 2651 current->text[current->length] = 0; 2652 } 2653 2654 if (sel) 2655 { 2656 if (cur_row + 1 == state->sel_end.row) 2657 state->sel_end.col 2658 += state->buffer[cur_row].length; 2659 if (cur_row + 1 == state->sel_start.row) 2660 state->sel_end.col -= state->sel_start.col; 2661 state->sel_end.row--; 2662 } 2663 simple_join_lines(state); 2664 end_row--; 2665 2666 current = &state->buffer[cur_row]; 2667 next = &state->buffer[cur_row + 1]; 2668 2669 dlen = display_length(current->text, current->length); 2670 } 2671 2672 /* If the line has wrap_width or more characters, wrap it */ 2673 while (dlen >= wrap_width) 2674 { 2675 int tdlen; 2676 bcol = buffer_length(current->text, wrap_width); 2677 while (bcol > 0 2678 && !u32_is_word_boundary(current->text[bcol - 1], 1)) 2679 bcol--; 2680 if (bcol == 0) 2681 break; 2682 ENSURE_SIZE(current->text, ptext, current->size, 2683 current->length + 1, current->length + LINE_ALLOC_DELTA, 2684 wrap_alloc_error, uint32_t); 2685 state->coord.col = bcol - 1; 2686 2687 if (sel) 2688 { 2689 if (cur_row == state->sel_end.row) 2690 state->sel_end.col -= bcol; 2691 /* TODO: Add startcol parameter to buffer_length? */ 2692 /*if (cur_row == state->sel_start.row) 2693 state->sel_end.col += state->sel_start.col;*/ 2694 state->sel_end.row++; 2695 } 2696 simple_insert_line(state); 2697 end_row++; 2698 2699 cur_row++; 2700 state->coord.row++; 2701 current = &state->buffer[cur_row]; 2702 next = &state->buffer[cur_row + 1]; 2703 2704 /* - Remove spaces between the two words at split 2705 * - current is the new current (the rest of the line 2706 * after split) */ 2707 while (u32_is_word_boundary(current->text[0], 1)) 2708 { 2709 for (col = 0; col < current->length; col++) 2710 current->text[col] = current->text[col + 1]; 2711 assert(current->length > 0); 2712 current->length--; 2713 if (current->length == 0) 2714 break; 2715 } 2716 2717 if (cur_row + 1 == state->rows_count) 2718 goto wrap_next_line; 2719 2720 /* If the rest of the line is still in block and is 2721 * < wrap_width, join it with next (the new next) */ 2722 tdlen = display_length(current->text, current->length); 2723 if ((current->length > 0) && u32_has_nonblanks(next->text) 2724 && (tdlen < wrap_width) 2725 && !(sel && (cur_row == state->sel_end.row))) 2726 { 2727 /* Add space if needed to avoid joining words */ 2728 /* clang-format off */ 2729 if (!u32_is_word_boundary( 2730 current->text[current->length - 1], 1)) 2731 { 2732 /* clang-format on */ 2733 ENSURE_SIZE(current->text, ptext, current->size, 2734 current->length + 2, 2735 current->length + 2, wrap_alloc_error, 2736 uint32_t); 2737 current->length++; 2738 current->text[current->length - 1] 2739 = (uint32_t)' '; 2740 current->text[current->length] = 0; 2741 } 2742 2743 if (sel) 2744 { 2745 if (cur_row + 1 == state->sel_end.row) 2746 state->sel_end.col 2747 += state->buffer[cur_row].length; 2748 if (cur_row + 1 == state->sel_start.row) 2749 state->sel_end.col 2750 -= state->sel_start.col; 2751 state->sel_end.row--; 2752 } 2753 simple_join_lines(state); 2754 end_row--; 2755 2756 dlen = display_length(current->text, 2757 sel && (cur_row == state->sel_end.row) 2758 ? state->sel_end.col 2759 : current->length); 2760 // dlen = display_length(current->text, current->length); 2761 if (dlen < wrap_width) 2762 goto wrap_next_line; 2763 2764 /* One more split needed for the rest of the line (new 2765 * current) */ 2766 bcol = buffer_length(current->text, wrap_width); 2767 while (bcol > 0 2768 && !u32_is_word_boundary(current->text[bcol - 1], 2769 1)) 2770 bcol--; 2771 if (bcol == 0) 2772 break; 2773 ENSURE_SIZE(current->text, ptext, current->size, 2774 current->length + 1, 2775 current->length + LINE_ALLOC_DELTA, 2776 wrap_alloc_error, uint32_t); 2777 state->coord.col = bcol - 1; 2778 2779 if (sel) 2780 { 2781 if (cur_row == state->sel_end.row) 2782 state->sel_end.col -= bcol; 2783 /* TODO: Add startcol parameter to 2784 * buffer_length? */ 2785 /*if (cur_row == state->sel_start.row) 2786 state->sel_end.col += 2787 state->sel_start.col;*/ 2788 state->sel_end.row++; 2789 } 2790 simple_insert_line(state); 2791 end_row++; 2792 2793 /* Remove spaces between the two words at split */ 2794 while (u32_is_word_boundary(next->text[0], 1)) 2795 { 2796 for (col = 0; col < next->length; col++) 2797 next->text[col] = next->text[col + 1]; 2798 assert(next->length > 0); 2799 next->length--; 2800 if (next->length == 0) 2801 break; 2802 } 2803 } 2804 2805 if (cur_row + 1 == state->rows_count) 2806 goto wrap_next_line; 2807 2808 if (cur_row == MIN((end_row + 1), state->rows_count)) 2809 break; 2810 2811 dlen = display_length(current->text, current->length); 2812 } 2813 cur_row++; 2814 if (added_blank) 2815 goto wrap_finish; 2816 if (cur_row >= state->rows_count) 2817 { 2818 added_blank = 1; 2819 move_end(state); 2820 simple_insert_line(state); 2821 move_up(state); 2822 goto wrap_next_line; 2823 } 2824 else if (cur_row < (end_row + 2 >= state->rows_count ? state->rows_count 2825 : end_row + 1)) 2826 goto wrap_next_line; 2827 2828 wrap_finish: 2829 if (added_blank) 2830 erase_line(state); 2831 2832 if (sel) 2833 position_to_selection_end(state); 2834 else 2835 position_to(state, cur_row, 0); 2836 2837 state->action = ACT_IDLE; 2838 recalculate_line_numbers(state); 2839 return 0; 2840 2841 wrap_not_enabled: 2842 state->error = ERR_NOT_IMP; 2843 return 1; 2844 2845 wrap_alloc_error: 2846 state->running = 0; 2847 return 1; 2848 } 2849 2850 static int 2851 write_file(struct DrawState* state) 2852 { 2853 assert(state != NULL); 2854 if (save_file(state)) 2855 { 2856 state->running = 0; 2857 return 1; 2858 } 2859 2860 state->error = ERR_NONE; 2861 state->dirty = 0; 2862 return 0; 2863 } 2864 2865 static int 2866 yank_to_paste(struct DrawState* state) 2867 { 2868 simple_yank(state); 2869 clear_selection(state); 2870 return 0; 2871 } 2872 2873 int 2874 main(int argc, char** argv) 2875 { 2876 struct tb_event ev; 2877 struct DrawState state; 2878 struct stat st; 2879 char prompt[LINE_DEFAULT]; 2880 char* filename = NULL; 2881 char* wd = NULL; 2882 char* dir = NULL; 2883 char* pdir = NULL; 2884 char* temp = NULL; 2885 char* rfn = NULL; 2886 const char* saved_tb_error = NULL; 2887 ssize_t filename_size = 0; 2888 int result = 0; 2889 int dir_access; 2890 int dir_access_errno; 2891 int saved_errno; 2892 int tbires; 2893 int optind = 1; 2894 2895 *prompt = 0; 2896 2897 #ifdef __OpenBSD__ 2898 if (pledge("stdio tty unveil rpath wpath cpath fattr", NULL) < 0) 2899 { 2900 perror(PROGRAM_NAME ": pledge"); 2901 exit(errno); 2902 } 2903 #endif 2904 2905 setlocale(LC_ALL, ""); 2906 2907 if (argc == 1) 2908 return print_error(CUSTOM_ERROR_START + ERR_NO_CL_ARG, 2909 "No filename given"); 2910 2911 main_options_loop: 2912 if (optind == argc) 2913 return print_error(CUSTOM_ERROR_START + ERR_NO_CL_ARG, 2914 "No filename given"); 2915 2916 if (!strcmp(argv[optind], "-h") || !strcmp(argv[optind], "--help")) 2917 { 2918 printf("Usage:\t%s -h | --help | -V | --full-version | " 2919 "-v | --version\n" 2920 "\t%s [-B | --no-backup] [-b | --backup] " 2921 "[-O | --no-open-binary] [-o | --open-binary] " 2922 "filename\n", 2923 PROGRAM_NAME, PROGRAM_NAME); 2924 return 0; 2925 } 2926 else if (!strcmp(argv[optind], "-v") 2927 || !strcmp(argv[optind], "--version")) 2928 { 2929 printf("%s %s, committed on %s\n", PROGRAM_NAME, VERSION, DATE); 2930 return 0; 2931 } 2932 else if (!strcmp(argv[optind], "-V") 2933 || !strcmp(argv[optind], "--full-version")) 2934 { 2935 printf("%s %s, committed on %s\n", PROGRAM_NAME, VERSION, DATE); 2936 puts(COPYRIGHT); 2937 printf("Features set in config.h and on command line:\n"); 2938 printf(" backup_fail:\t\t%s\n", backup_fail ? "set" : "unset"); 2939 printf(" create_backups:\t%s\n", 2940 create_backups ? "set" : "unset"); 2941 printf(" enable_wrap:\t\t%s\n", enable_wrap ? "set" : "unset"); 2942 printf(" line_numbers:\t\t%s\n", 2943 line_numbers ? "set" : "unset"); 2944 printf(" open_binary:\t\t%s\n", open_binary ? "set" : "unset"); 2945 printf(" wrap_width:\t\t%d\n", wrap_width); 2946 return 0; 2947 } 2948 else if (!strcmp(argv[optind], "-B") 2949 || !strcmp(argv[optind], "--no-backup")) 2950 { 2951 create_backups = 0; 2952 optind++; 2953 goto main_options_loop; 2954 } 2955 else if (!strcmp(argv[optind], "-b") 2956 || !strcmp(argv[optind], "--backup")) 2957 { 2958 create_backups = 1; 2959 optind++; 2960 goto main_options_loop; 2961 } 2962 else if (!strcmp(argv[optind], "-O") 2963 || !strcmp(argv[optind], "--no-open-binary")) 2964 { 2965 open_binary = 0; 2966 optind++; 2967 goto main_options_loop; 2968 } 2969 else if (!strcmp(argv[optind], "-o") 2970 || !strcmp(argv[optind], "--open-binary")) 2971 { 2972 open_binary = 1; 2973 optind++; 2974 goto main_options_loop; 2975 } 2976 2977 /* 2978 * Chicken and egg problem: to get fpathconf(dirfd, _PC_PATH_MAX) 2979 * we need a dir, which needs to be allocated, and to allocate it we 2980 * need to know the value returned by fpathconf(dirfd, _PC_PATH_MAX)... 2981 */ 2982 /* argv[1] is the filename; BTW, "filename" is a misnomer */ 2983 filename_size = strlen(argv[optind]) + 1; 2984 if (!(dir = malloc(filename_size))) 2985 { 2986 perror(PROGRAM_NAME ": malloc"); 2987 exit(errno); 2988 } 2989 2990 (void)dirname(dir, argv[optind], filename_size); 2991 if (init_limits(dir) < 0) 2992 { 2993 perror(PROGRAM_NAME ": init_limits"); 2994 free(dir); 2995 exit(errno); 2996 } 2997 2998 filename = calloc(limits[L_PATH_MAX], 1); 2999 if (!filename) 3000 { 3001 perror(PROGRAM_NAME ": calloc"); 3002 exit(errno); 3003 } 3004 3005 *filename = 0; 3006 MEMCCPY(filename, argv[optind], limits[L_PATH_MAX], temp); 3007 if (!*filename) 3008 { 3009 free(filename); 3010 free(dir); 3011 return print_error(EINVAL, "No filename given"); 3012 } 3013 3014 init_drawstate(&state, filename, prompt, 0, 0); 3015 3016 #ifdef __OpenBSD__ 3017 if (unveil(state.filename, "rwc") < 0) 3018 { 3019 perror(PROGRAM_NAME ": unveil"); 3020 goto main_done; 3021 } 3022 if (unveil(dir, "rwc") < 0) 3023 { 3024 perror(PROGRAM_NAME ": unveil"); 3025 goto main_done; 3026 } 3027 #endif 3028 3029 if (!create_backups) 3030 goto main_skip_backups; 3031 3032 if (!(wd = malloc(limits[L_PATH_MAX]))) 3033 { 3034 perror(PROGRAM_NAME ": malloc"); 3035 goto main_done; 3036 } 3037 if (!getcwd(wd, limits[L_PATH_MAX])) 3038 { 3039 perror(PROGRAM_NAME ": getcwd"); 3040 goto main_done; 3041 } 3042 #ifdef __OpenBSD__ 3043 if (unveil(wd, "rwc") < 0) 3044 { 3045 perror(PROGRAM_NAME ": unveil"); 3046 goto main_done; 3047 } 3048 #endif 3049 3050 errno = 0; 3051 if (lstat(state.filename, &st) != -1) 3052 { 3053 if (S_ISLNK(st.st_mode)) 3054 { 3055 if (!(rfn = malloc(limits[L_PATH_MAX]))) 3056 { 3057 perror(PROGRAM_NAME ": malloc"); 3058 goto main_done; 3059 } 3060 3061 if (!(state.real_filename = malloc(limits[L_PATH_MAX]))) 3062 { 3063 perror(PROGRAM_NAME ": malloc"); 3064 goto main_done; 3065 } 3066 3067 state.rfn_len = readlink(state.filename, rfn, 3068 limits[L_PATH_MAX]); 3069 if (state.rfn_len == -1) 3070 goto main_done; 3071 3072 rfn[state.rfn_len] = 0; 3073 3074 if (*rfn == '/') /* rfn is absolute */ 3075 { 3076 MEMCCPY(state.real_filename, rfn, 3077 limits[L_PATH_MAX], temp); 3078 state.rfn_len = strlen(rfn); 3079 } 3080 else /* rfn is relative */ 3081 { 3082 size_t rfn_dir_len; 3083 3084 state.orig_real_filename 3085 = strndup(rfn, state.rfn_len); 3086 if (!state.orig_real_filename) 3087 { 3088 perror(PROGRAM_NAME ": strndup"); 3089 goto main_done; 3090 } 3091 3092 MEMCCPY(state.real_filename, dir, 3093 limits[L_PATH_MAX], temp); 3094 if (!temp) 3095 { 3096 errno = ENAMETOOLONG; 3097 goto main_done; 3098 } 3099 *(temp - 1) = '/'; /* overwrite NUL */ 3100 rfn_dir_len = temp - state.real_filename - 1; 3101 MEMCCPY_EXT(temp, state.real_filename, rfn, 3102 limits[L_PATH_MAX] - rfn_dir_len, temp); 3103 if (temp) 3104 state.rfn_len = temp 3105 - state.real_filename - 1; 3106 } 3107 free(rfn); 3108 rfn = NULL; 3109 #ifdef __OpenBSD__ 3110 if (unveil(state.real_filename, "rwc") < 0) 3111 { 3112 perror(PROGRAM_NAME ": unveil"); 3113 goto main_done; 3114 } 3115 #endif 3116 if (stat(state.real_filename, &st) == -1) 3117 goto main_done; 3118 } 3119 } /* We don't error out here on ENOENT to account for new files */ 3120 else if (errno == ENOENT) 3121 state.new_file = 1; 3122 else 3123 goto main_done; 3124 3125 if (!(state.backup_filename = malloc(limits[L_PATH_MAX]))) 3126 { 3127 perror(PROGRAM_NAME ": malloc"); 3128 goto main_done; 3129 } 3130 3131 /* Backup filename is the target file + suffix if the file to 3132 * write is not a new file and is a symlink, otherwise file + 3133 * suffix */ 3134 MEMCCPY(state.backup_filename, 3135 state.new_file ? state.filename 3136 : (state.rfn_len > 0 ? state.real_filename 3137 : state.filename), 3138 limits[L_PATH_MAX], temp); 3139 if (!temp) /* filename is full, turning off backups */ 3140 state.backup_possible = 0; 3141 else 3142 MEMCCPY_EXT((temp - 1), state.backup_filename, backup_suffix, 3143 limits[L_PATH_MAX] + temp - state.backup_filename, 3144 temp); 3145 #ifdef __OpenBSD__ 3146 if (unveil(state.backup_filename, "rwc") < 0) 3147 { 3148 perror(PROGRAM_NAME ": unveil"); 3149 goto main_done; 3150 } 3151 #endif 3152 3153 main_skip_backups: 3154 if (state.rfn_len > 0) 3155 { 3156 if (!(pdir = realloc(dir, limits[L_PATH_MAX]))) 3157 { 3158 perror(PROGRAM_NAME ": realloc"); 3159 goto main_done; 3160 } 3161 dir = pdir; 3162 *dir = 0; 3163 (void)dirname(dir, state.real_filename, limits[L_PATH_MAX]); 3164 #ifdef __OpenBSD__ 3165 if (unveil(dir, "rwc") < 0) 3166 { 3167 perror(PROGRAM_NAME ": unveil"); 3168 goto main_done; 3169 } 3170 #endif 3171 } 3172 3173 #ifdef __OpenBSD__ 3174 if (unveil_terminfo_path() < 0) 3175 { 3176 perror(PROGRAM_NAME ": unveil"); 3177 goto main_done; 3178 } 3179 /* Have to explicitly unveil /dev/tty before tb_init despite pledge(tty) 3180 * in OpenBSD 7.9+ 3181 */ 3182 if (unveil("/dev/tty", "rw")) 3183 { 3184 perror(PROGRAM_NAME ": unveil"); 3185 goto main_done; 3186 } 3187 if (unveil(NULL, NULL) < 0) 3188 { 3189 perror(PROGRAM_NAME ": unveil"); 3190 goto main_done; 3191 } 3192 #endif 3193 3194 dir_access = access(dir, W_OK | X_OK); 3195 dir_access_errno = errno; 3196 3197 if (!backup_fail && state.backup_possible) 3198 state.backup_possible = dir_access != -1; 3199 3200 result = stat(state.filename, &st); 3201 if (result == -1) 3202 { 3203 if (errno != ENOENT || dir_access == -1) 3204 { 3205 errno = dir_access_errno; 3206 goto main_done; 3207 } 3208 if (create_file(&state)) 3209 goto main_done; 3210 3211 goto init_termbox; 3212 } 3213 3214 errno = 0; 3215 3216 if (!S_ISREG(st.st_mode)) 3217 { 3218 state.error = ERR_NOTREG_FILE; 3219 goto main_done; 3220 } 3221 3222 if (access(state.filename, F_OK | R_OK | W_OK) == -1) 3223 goto main_done; 3224 3225 if (state.rfn_len > 0 3226 && access(state.real_filename, F_OK | R_OK | W_OK) == -1) 3227 goto main_done; 3228 3229 if (load_file(&state)) 3230 goto main_done; 3231 3232 goto init_termbox; 3233 3234 init_termbox: 3235 tbires = 0; 3236 if ((tbires = tb_init())) 3237 { 3238 saved_tb_error = tb_strerror(tbires); 3239 errno = ERR_TB_ERROR; 3240 goto main_done; 3241 } 3242 3243 errno = 0; 3244 state.maxx = 0; 3245 state.recalculate_size = 1; 3246 3247 while (state.running) 3248 { 3249 tb_set_clear_attrs(colors[C_TEXT].fg, colors[C_TEXT].bg); 3250 tb_clear(); 3251 3252 if (state.recalculate_size) 3253 { 3254 recalculate_size(&state); 3255 state.recalculate_size = 0; 3256 } 3257 if (draw(&state)) 3258 { 3259 state.running = 0; 3260 goto main_done; 3261 } 3262 tb_present(); 3263 result = tb_poll_event(&ev); 3264 if (result == TB_OK && ev.type == TB_EVENT_RESIZE) 3265 state.recalculate_size = 1; 3266 else if (result == TB_OK && ev.type == TB_EVENT_KEY) 3267 handle_key_event(&ev, &state); 3268 } 3269 3270 main_done: 3271 saved_errno = errno; 3272 3273 if (saved_tb_error) 3274 { 3275 tb_shutdown(1); 3276 print_error(1, saved_tb_error); 3277 exit(CUSTOM_ERROR_START + saved_errno); 3278 } 3279 3280 tb_shutdown(saved_errno != ENOMEM); 3281 3282 if (saved_errno) 3283 perror(PROGRAM_NAME); 3284 3285 if (state.error == ERR_NOTREG_FILE) 3286 print_error(1, errors[state.error], state.filename); 3287 else if (state.error) 3288 print_error(1, errors[state.error]); 3289 /*free_drawstate(&state); 3290 free(wd); 3291 free(dir); 3292 free(rfn);*/ 3293 return saved_errno 3294 ? saved_errno 3295 : (state.error ? CUSTOM_ERROR_START + state.error : 0); 3296 }