ledit.c (25669B)
1 /* FIXME: generally optimize redrawing */ 2 /* FIXME: Just use int for everything? size_t just doesn't seem to be worth it */ 3 /* FIXME: Make scrolling more smooth */ 4 /* FIXME: Only redraw part of screen if needed */ 5 /* FIXME: overflow in repeated commands */ 6 /* FIXME: Use PANGO_PIXELS() */ 7 /* FIXME: Fix cursor movement, especially buffer->trailing and writing at end of line */ 8 /* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */ 9 /* TODO: allow extending selection with shift+mouse like in e.g. gtk */ 10 11 #include <pwd.h> 12 #include <time.h> 13 #if TEST 14 #include <fcntl.h> 15 #endif 16 #include <errno.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <locale.h> 21 #include <unistd.h> 22 #include <sys/stat.h> 23 24 #include <X11/Xlib.h> 25 #include <X11/XKBlib.h> 26 #include <X11/extensions/Xdbe.h> 27 #include <X11/extensions/XKBrules.h> 28 29 #include "util.h" 30 #include "view.h" 31 #include "buffer.h" 32 #include "common.h" 33 #include "window.h" 34 #include "search.h" 35 #include "macros.h" 36 #include "memory.h" 37 #include "config.h" 38 #include "cleanup.h" 39 #include "keys.h" 40 #include "keys_basic.h" 41 #include "keys_command.h" 42 #include "configparser.h" 43 44 static void mainloop(void); 45 static void setup(int argc, char *argv[]); 46 static void redraw(void); 47 48 static void change_keyboard(char *lang); 49 static void key_press_event(ledit_view *view, XEvent *event); 50 static void key_press(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int n); 51 52 ledit_common common; 53 ledit_clipboard *clipboard = NULL; 54 ledit_buffer *buffer = NULL; 55 size_t cur_lang = 0; 56 57 #if TEST 58 static struct { 59 char *read; /* text read from stdin */ 60 size_t read_len; /* length of text in read buffer */ 61 size_t read_alloc; /* size of read buffer */ 62 size_t line_start; /* start of current line */ 63 size_t read_cur; /* length of text already read */ 64 } test_status = {NULL, 0, 0, 0, 0}; 65 66 #define READ_BLK_SIZE 128 67 68 /* Read up to READ_BLK_SIZE bytes from stdin. 69 Returns 1 if an error occurred, -1 if not new data available, 0 otherwise. */ 70 static int 71 read_input(void) { 72 if (test_status.read_cur > 0) { 73 memmove(test_status.read, test_status.read + test_status.read_cur, test_status.read_len - test_status.read_cur); 74 test_status.read_len -= test_status.read_cur; 75 test_status.read_cur = 0; 76 } 77 int nread; 78 test_status.read_alloc = ideal_array_size(test_status.read_alloc, test_status.read_len + READ_BLK_SIZE); 79 test_status.read = ledit_realloc(test_status.read, test_status.read_alloc); 80 nread = read(fileno(stdin), test_status.read + test_status.read_len, READ_BLK_SIZE); 81 if (nread == -1 && errno == EAGAIN) 82 return -1; 83 else if (nread == -1 || nread == 0) 84 return 1; 85 test_status.read_len += nread; 86 87 return 0; 88 } 89 90 /* based partially on OpenBSD's strtonum */ 91 static int 92 read_rangeint(long long *ret, int end, long long min, long long max) { 93 if (test_status.read_cur >= test_status.read_len || test_status.read[test_status.read_cur] != ' ') 94 return 1; 95 char end_char = end ? '\n' : ' '; 96 size_t len = 0; 97 test_status.read_cur++; 98 char *str = test_status.read + test_status.read_cur; 99 int found = 0; 100 for (; test_status.read_cur < test_status.read_len; test_status.read_cur++) { 101 if (test_status.read[test_status.read_cur] == end_char) { 102 found = 1; 103 break; 104 } 105 len++; 106 } 107 if (!found || len == 0) 108 return 1; 109 /* the string needs to be nul-terminated 110 if it contains more than 11 characters (10 digits + sign), 111 it's illegal anyways (at least for these testing purposes...) */ 112 if (len > 11) 113 return 1; 114 char nstr[12]; 115 strncpy(nstr, str, len); 116 nstr[len] = '\0'; 117 char *num_end; 118 long long ll = strtoll(nstr, &num_end, 10); 119 if (nstr == num_end || *num_end != '\0' || 120 ll < min || ll > max || ((ll == LLONG_MIN || 121 ll == LLONG_MAX) && errno == ERANGE)) { 122 return 1; 123 } 124 *ret = ll; 125 if (end) 126 test_status.read_cur++; 127 return 0; 128 } 129 130 static int 131 read_uint(unsigned int *ret, int end) { 132 long long l; 133 int err = read_rangeint(&l, end, 0, UINT_MAX); 134 *ret = (unsigned int)l; 135 return err; 136 } 137 138 static int 139 read_int(int *ret, int end) { 140 long long l; 141 int err = read_rangeint(&l, end, INT_MIN, INT_MAX); 142 *ret = (int)l; 143 return err; 144 } 145 146 static int 147 read_text(char **text, size_t *text_len) { 148 if (test_status.read_cur >= test_status.read_len || test_status.read[test_status.read_cur] != ' ') 149 return 1; 150 int bs = 0; 151 int offset = 0; 152 test_status.read_cur++; 153 size_t start = test_status.read_cur; 154 *text = test_status.read + test_status.read_cur; 155 int found = 0; 156 for (; test_status.read_cur < test_status.read_len; test_status.read_cur++) { 157 if (test_status.read[test_status.read_cur] == '\\') { 158 bs++; 159 if (bs / 2) 160 offset++; 161 bs %= 2; 162 test_status.read[test_status.read_cur - offset] = '\\'; 163 } else if (test_status.read[test_status.read_cur] == '\n') { 164 if (!bs) { 165 found = 1; 166 break; 167 } else { 168 bs = 0; 169 offset++; 170 test_status.read[test_status.read_cur - offset] = '\n'; 171 } 172 } else { 173 test_status.read[test_status.read_cur - offset] = test_status.read[test_status.read_cur]; 174 bs = 0; 175 } 176 } 177 if (!found) 178 return 1; 179 *text_len = test_status.read_cur - start - offset; 180 test_status.read_cur++; 181 return 0; 182 } 183 184 static int 185 read_filename(char **text, size_t *text_len) { 186 if (read_text(text, text_len)) 187 return 1; 188 for (size_t i = 0; i < *text_len; i++) { 189 if ((*text)[i] == '/' || (*text)[i] == '\0') 190 return 1; 191 } 192 return 0; 193 } 194 195 static unsigned int view_num = 0; 196 /* Process commands in test_status. 197 Returns 0 if no complete commands are contained in read buffer, 1 otherwise. */ 198 static int 199 process_commands(void) { 200 int bs = 0; 201 int found = 0; 202 size_t nl_index = 0; 203 for (size_t i = test_status.read_cur; i < test_status.read_len; i++) { 204 if (test_status.read[i] == '\\') { 205 bs++; 206 bs %= 2; 207 } else if (test_status.read[i] == '\n' && bs == 0) { 208 found = 1; 209 nl_index = i; 210 break; 211 } else { 212 bs = 0; 213 } 214 } 215 if (!found) 216 return 0; 217 unsigned int key_state, button_num, keysym, new_view; 218 char *text, *term, *errstr; 219 size_t text_len; 220 int x, y; 221 XEvent e; 222 FILE *file; 223 test_status.read_cur += 1; 224 ledit_view *view = buffer->views[view_num]; 225 switch (test_status.read[test_status.read_cur-1]) { 226 case 'k': 227 /* key press */ 228 /* k key_state keysym text */ 229 if (read_uint(&key_state, 0)) 230 goto error; 231 if (read_uint(&keysym, 0)) 232 goto error; 233 if (read_text(&text, &text_len)) 234 goto error; 235 key_press(view, key_state, keysym, text, (int)text_len); 236 break; 237 case 'p': 238 /* mouse button press */ 239 /* p button_num x y */ 240 if (read_uint(&button_num, 0)) 241 goto error; 242 if (read_int(&x, 0)) 243 goto error; 244 if (read_int(&y, 1)) 245 goto error; 246 e = (XEvent){.xbutton = {.type = ButtonPress, .button = button_num, .x = x, .y = y}}; 247 window_register_button_press(view->window, &e); 248 break; 249 case 'r': 250 /* mouse button release */ 251 /* r button_num x y */ 252 if (read_uint(&button_num, 0)) 253 goto error; 254 if (read_int(&x, 0)) 255 goto error; 256 if (read_int(&y, 1)) 257 goto error; 258 e = (XEvent){.xbutton = {.type = ButtonRelease, .button = button_num, .x = x, .y = y}}; 259 window_button_release(view->window, &e); 260 break; 261 case 'm': 262 /* mouse motion */ 263 /* m x y */ 264 if (read_int(&x, 0)) 265 goto error; 266 if (read_int(&y, 1)) 267 goto error; 268 e = (XEvent){.xmotion = {.type = MotionNotify, .x = x, .y = y}}; 269 window_register_motion(view->window, &e); 270 break; 271 case 'l': 272 /* language switch */ 273 /* l lang_name */ 274 if (read_text(&text, &text_len)) 275 goto error; 276 term = ledit_strndup(text, text_len); 277 change_keyboard(term); 278 free(term); 279 break; 280 case 's': 281 /* switch view */ 282 /* s view_num */ 283 if (read_uint(&new_view, 1)) 284 goto error; 285 if (new_view >= buffer->views_num) 286 fprintf(stderr, "Invalid view number %u\n", new_view); 287 else 288 view_num = new_view; 289 break; 290 case 'w': 291 /* write contents of buffer */ 292 /* w file_name */ 293 if (read_filename(&text, &text_len)) 294 goto error; 295 term = ledit_strndup(text, text_len); 296 if (buffer_write_to_filename(buffer, term, &errstr)) 297 fprintf(stderr, "Error writing %s: %s\n", term, errstr); 298 free(term); 299 break; 300 case 'd': 301 /* dump other info to file */ 302 /* d file_name */ 303 if (read_filename(&text, &text_len)) 304 goto error; 305 term = ledit_strndup(text, text_len); 306 file = fopen(term, "w"); 307 if (!file) { 308 fprintf(stderr, "Unable to open file %s\n", term); 309 } else { 310 fprintf( 311 file, 312 "cursor_line: %zu, cursor_byte: %zu, sel_valid: %d, " 313 "sel_line1: %zu, sel_byte1: %zu, " 314 "sel_line2: %zu, sel_byte2: %zu\n", 315 view->cur_line, view->cur_index, view->sel_valid, 316 view->sel.line1, view->sel.byte1, 317 view->sel.line2, view->sel.byte2 318 ); 319 fclose(file); 320 } 321 free(term); 322 break; 323 case 'u': 324 /* dump undo stack to file */ 325 if (read_filename(&text, &text_len)) 326 goto error; 327 /* u file_name */ 328 term = ledit_strndup(text, text_len); 329 file = fopen(term, "w"); 330 if (!file) { 331 fprintf(stderr, "Unable to open file %s\n", term); 332 } else { 333 dump_undo_stack(file, buffer->undo); 334 fclose(file); 335 } 336 free(term); 337 break; 338 default: 339 goto error; 340 } 341 return 1; 342 error: 343 fprintf(stderr, "Error parsing command.\n"); 344 test_status.read_cur = nl_index + 1; 345 return 1; 346 } 347 #endif 348 349 /* can only be set to 1 when compiled with TEST */ 350 static int test_extra = 0; 351 352 static void 353 mainloop(void) { 354 #if TEST 355 int flags = fcntl(fileno(stdin), F_GETFL, 0); 356 if (flags == -1) { 357 fprintf(stderr, "Unable to set non-blocking mode on stdin.\n"); 358 return; 359 } 360 if (fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK)) { 361 fprintf(stderr, "Unable to set non-blocking mode on stdin.\n"); 362 return; 363 } 364 #endif 365 XEvent event; 366 int xkb_event_type; 367 int major, minor; 368 if (!XkbQueryExtension(common.dpy, 0, &xkb_event_type, NULL, &major, &minor)) { 369 fprintf(stderr, "XKB not supported."); 370 ledit_cleanup(); 371 exit(1); 372 } 373 /*printf("XKB (%d.%d) supported.\n", major, minor);*/ 374 /* This should select the events when the keyboard mapping changes. 375 * When e.g. 'setxkbmap us' is executed, two events are sent, but I 376 * haven't figured out how to change that. When the xkb layout 377 * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'), 378 * this issue does not occur because only a state event is sent. */ 379 XkbSelectEvents( 380 common.dpy, XkbUseCoreKbd, 381 XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask 382 ); 383 XkbSelectEventDetails( 384 common.dpy, XkbUseCoreKbd, XkbStateNotify, 385 XkbAllStateComponentsMask, XkbGroupStateMask 386 ); 387 XSync(common.dpy, False); 388 int running = 1; 389 int change_kbd = 1; 390 391 redraw(); 392 /* store last draw time so framerate can be limited */ 393 struct timespec now, elapsed, last, sleep_time; 394 clock_gettime(CLOCK_MONOTONIC, &last); 395 sleep_time.tv_sec = 0; 396 while (running) { 397 /* This "lazy destroying" is not entirely ideal yet, but it's 398 necessary to avoid a crash when closing a view (I'm not 399 entirely sure what exactly causes the crash) 400 -> Update: The cause of the crash was something different, 401 but I'm still leaving it as is for now because there 402 may be other reasons for doing it lazily. */ 403 for (size_t i = 0; i < buffer->views_num; i++) { 404 if (buffer->views[i]->destroy) { 405 buffer_remove_view(buffer, buffer->views[i]); 406 if (buffer->views_num == 0) { 407 ledit_cleanup(); 408 exit(0); 409 } 410 /* only delete one - otherwise, 411 the loop would need to be 412 modified 413 I guess it's unrealistic to 414 assume that the deletion cmd 415 will be called multiple times 416 in such a short time anyways */ 417 break; 418 } 419 } 420 while (XPending(common.dpy)) { 421 XNextEvent(common.dpy, &event); 422 if (event.type == xkb_event_type) { 423 change_kbd = 1; 424 continue; 425 } 426 if (clipboard_filter_event(clipboard, &event)) 427 continue; 428 if (XFilterEvent(&event, None)) 429 continue; 430 ledit_view *view = NULL; 431 /* FIXME: abstract view handling a bit (don't access directly here) */ 432 for (size_t i = 0; i < buffer->views_num; i++) { 433 if (buffer->views[i]->window->xwin == event.xany.window) { 434 view = buffer->views[i]; 435 break; 436 } 437 } 438 if (view == NULL) 439 continue; /* shouldn't happen */ 440 ledit_window *window = view->window; 441 switch (event.type) { 442 case Expose: 443 view->redraw = 1; 444 break; 445 case ConfigureNotify: 446 window_register_resize(view->window, &event); 447 break; 448 case ButtonPress: 449 if (!test_extra) 450 window_register_button_press(view->window, &event); 451 break; 452 case ButtonRelease: 453 if (!test_extra) 454 window_button_release(view->window, &event); 455 break; 456 case MotionNotify: 457 if (!test_extra) 458 window_register_motion(window, &event); 459 break; 460 case KeyPress: 461 if (!test_extra) 462 key_press_event(view, &event); 463 break; 464 case ClientMessage: 465 if ((Atom)event.xclient.data.l[0] == view->window->wm_delete_msg) { 466 buffer_remove_view(buffer, view); 467 if (buffer->views_num == 0) 468 running = 0; 469 } 470 break; 471 default: 472 break; 473 } 474 }; 475 476 #if TEST 477 int ret; 478 if ((ret = read_input()) == 1) { 479 fprintf(stderr, "Unable to read text from stdin.\n"); 480 } else if (ret == 0) { 481 while (process_commands()) { 482 /* NOP */ 483 } 484 } 485 #endif 486 487 for (size_t i = 0; i < buffer->views_num; i++) { 488 window_handle_filtered_events(buffer->views[i]->window); 489 } 490 491 if (!test_extra && change_kbd) { 492 change_kbd = 0; 493 XkbDescPtr desc = XkbGetMap( 494 common.dpy, 0, XkbUseCoreKbd 495 ); 496 if (!desc || XkbGetNames(common.dpy, XkbGroupNamesMask, desc) != Success) { 497 /* FIXME: maybe show this as error message in windows */ 498 fprintf( 499 stderr, 500 "Unable to obtain keyboard layout information.\n" 501 ); 502 if (desc) 503 XkbFreeClientMap(desc, 0, True); 504 } else { 505 XkbStateRec s; 506 XkbGetState(common.dpy, XkbUseCoreKbd, &s); 507 char *group = XGetAtomName( 508 common.dpy, desc->names->groups[s.group] 509 ); 510 change_keyboard(group); 511 XFree(group); 512 XkbFreeNames(desc, XkbGroupNamesMask, True); 513 XkbFreeClientMap(desc, 0, True); 514 } 515 } 516 redraw(); 517 518 clock_gettime(CLOCK_MONOTONIC, &now); 519 ledit_timespecsub(&now, &last, &elapsed); 520 if (elapsed.tv_sec == 0 && elapsed.tv_nsec < TICK) { 521 sleep_time.tv_nsec = TICK - elapsed.tv_nsec; 522 nanosleep(&sleep_time, NULL); 523 } 524 last = now; 525 } 526 } 527 528 extern char *optarg; 529 extern int optind; 530 531 static void 532 setup(int argc, char *argv[]) { 533 setlocale(LC_CTYPE, ""); 534 XSetLocaleModifiers(""); 535 536 char c; 537 char *opt_filename = NULL; 538 #if TEST 539 char *opts = "tc:"; 540 #else 541 char *opts = "c:"; 542 #endif 543 while ((c = getopt(argc, argv, opts)) != -1) { 544 switch (c) { 545 case 'c': 546 opt_filename = optarg; 547 break; 548 #if TEST 549 case 't': 550 test_extra = 1; 551 break; 552 #endif 553 default: 554 fprintf(stderr, "USAGE: ledit [-c config] [file]\n"); 555 exit(1); 556 break; 557 } 558 } 559 argc -= optind; 560 argv += optind; 561 562 common.dpy = XOpenDisplay(NULL); 563 common.screen = DefaultScreen(common.dpy); 564 /* FIXME: fallback when no db support */ 565 /* based on http://wili.cc/blog/xdbe.html */ 566 int major, minor; 567 if (XdbeQueryExtension(common.dpy, &major, &minor)) { 568 int num_screens = 1; 569 Drawable screens[] = {DefaultRootWindow(common.dpy)}; 570 XdbeScreenVisualInfo *info = XdbeGetVisualInfo( 571 common.dpy, screens, &num_screens 572 ); 573 if (!info || num_screens < 1 || info->count < 1) { 574 fprintf(stderr, "No visuals support Xdbe.\n"); 575 ledit_cleanup(); 576 exit(1); 577 } 578 XVisualInfo xvisinfo_templ; 579 /* we know there's at least one */ 580 xvisinfo_templ.visualid = info->visinfo[0].visual; 581 xvisinfo_templ.screen = 0; 582 xvisinfo_templ.depth = info->visinfo[0].depth; 583 int matches; 584 XVisualInfo *xvisinfo_match = XGetVisualInfo( 585 common.dpy, 586 VisualIDMask | VisualScreenMask | VisualDepthMask, 587 &xvisinfo_templ, &matches 588 ); 589 if (!xvisinfo_match || matches < 1) { 590 fprintf( 591 stderr, 592 "Couldn't match a Visual with double buffering\n" 593 ); 594 ledit_cleanup(); 595 exit(1); 596 } 597 common.vis = xvisinfo_match->visual; 598 XFree(xvisinfo_match); 599 XdbeFreeVisualInfo(info); 600 } else { 601 fprintf(stderr, "No Xdbe support.\n"); 602 ledit_cleanup(); 603 exit(1); 604 } 605 606 common.depth = DefaultDepth(common.dpy, common.screen); 607 common.cm = DefaultColormap(common.dpy, common.screen); 608 609 #ifdef LEDIT_DEBUG 610 struct timespec now, elapsed, last; 611 clock_gettime(CLOCK_MONOTONIC, &last); 612 #endif 613 614 /* FIXME: Technically, there's a race condition between checking stat and actually 615 opening the files. This is mainly important when checking if the file is not a regular 616 file because that is not an error for functions like fopen, so bad things will happen 617 if a non-regular file (e.g. a directory) is given to one of the file reading 618 functions. However, I don't know of any portable way to have one of the open functions 619 check that, so this is the best I can do. */ 620 char *stat_errstr = NULL, *load_errstr = NULL, *load_default_errstr = NULL; 621 char *cfgfile = NULL; 622 if (!opt_filename) { 623 uid_t uid = getuid(); 624 struct passwd *pw = getpwuid(uid); 625 if (!pw) { 626 stat_errstr = ledit_strdup("Unable to determine home directory to load default configuration file."); 627 } else { 628 cfgfile = ledit_strcat(pw->pw_dir, "/.leditrc"); 629 struct stat cfgst; 630 if (stat(cfgfile, &cfgst)) { 631 free(cfgfile); 632 cfgfile = NULL; 633 if (errno != ENOENT) { 634 stat_errstr = print_fmt("Unable to load configuration file '~/.leditrc': %s", strerror(errno)); 635 } 636 } else if (!S_ISREG(cfgst.st_mode)) { 637 stat_errstr = ledit_strdup("Unable to load configuration file '~/.leditrc': Is not a regular file."); 638 free(cfgfile); 639 cfgfile = NULL; 640 } 641 } 642 } else { 643 struct stat cfgst; 644 if (stat(opt_filename, &cfgst)) { 645 stat_errstr = print_fmt("Unable to load configuration file '%s': %s", opt_filename, strerror(errno)); 646 } else if (!S_ISREG(cfgst.st_mode)) { 647 stat_errstr = print_fmt("Unable to load configuration file '%s': Is not a regular file.", opt_filename); 648 } else { 649 cfgfile = ledit_strdup(opt_filename); 650 } 651 } 652 if (stat_errstr) 653 fprintf(stderr, "%s\n", stat_errstr); 654 if (config_loadfile(&common, cfgfile, &load_errstr)) { 655 fprintf(stderr, "%s\n", load_errstr); 656 fprintf(stderr, "Unable to load configuration '%s'\n", cfgfile ? cfgfile : "default config"); 657 int failure = 1; 658 if (cfgfile) { 659 /* retry with default config */ 660 failure = config_loadfile(&common, NULL, &load_default_errstr); 661 } 662 if (failure) { 663 if (load_default_errstr) { 664 fprintf(stderr, "%s\n", load_default_errstr); 665 fprintf(stderr, "Also unable to load default configuration\n"); 666 } 667 free(stat_errstr); 668 free(load_errstr); 669 free(load_default_errstr); 670 ledit_cleanup(); 671 exit(1); 672 } 673 } 674 free(load_default_errstr); 675 free(cfgfile); 676 677 #ifdef LEDIT_DEBUG 678 clock_gettime(CLOCK_MONOTONIC, &now); 679 ledit_timespecsub(&now, &last, &elapsed); 680 ledit_debug_fmt("Time to load config (total): %lld seconds, %ld nanoseconds\n", (long long)elapsed.tv_sec, elapsed.tv_nsec); 681 #endif 682 683 clipboard = clipboard_create(&common); 684 685 buffer = buffer_create(&common, clipboard); 686 buffer_add_view(buffer, NORMAL, 0, 0, 0); 687 /* FIXME: don't access view directly here */ 688 ledit_view *view = buffer->views[0]; 689 /* FIXME: this message may be wiped immediately */ 690 /* -> maybe allow showing multiple messages? */ 691 /* currently, the more important message is just prioritized */ 692 int show_error = 0; 693 if (stat_errstr || load_errstr) { 694 show_error = 1; 695 if (stat_errstr) 696 window_show_message(view->window, stat_errstr, -1); 697 else if (load_errstr) 698 window_show_message(view->window, load_errstr, -1); 699 free(stat_errstr); 700 free(load_errstr); 701 } 702 view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); 703 /* FIXME: maybe also log all errors instead of just showing them on screen? */ 704 /* FIXME: Support multiple buffers/files */ 705 /* FIXME: check if file may be binary */ 706 if (argc >= 1) { 707 /* FIXME: move this to different file */ 708 char *load_err; 709 struct stat sb; 710 int newfile = 0; 711 int readonly = 0; 712 int error = 0; 713 /* FIXME: maybe copy vi and open file in /tmp by default? */ 714 /* FIXME: when other methods of opening files (:r, etc.) are supported, 715 all this checking needs to be moved to a place where it can be reused */ 716 if (stat(argv[0], &sb)) { 717 if (errno == ENOENT) { 718 /* note that there may still be a failure 719 when trying to write if a directory in 720 the path does not exist */ 721 newfile = 1; 722 } else { 723 fprintf( 724 stderr, "Error opening file '%s': %s\n", 725 argv[0], strerror(errno) 726 ); 727 window_show_message_fmt( 728 view->window, "Error opening file '%s': %s", 729 argv[0], strerror(errno) 730 ); 731 error = 1; 732 } 733 } else if (!S_ISREG(sb.st_mode)) { 734 fprintf(stderr, "Error opening file '%s': Is not a regular file\n", argv[0]); 735 window_show_message_fmt( 736 view->window, "Error opening file '%s': Is not a regular file", argv[0] 737 ); 738 error = 1; 739 } 740 if (access(argv[0], W_OK)) { 741 readonly = 1; 742 } 743 if (!newfile && !error) { 744 if (buffer_load_file(buffer, argv[0], 0, &load_err)) { 745 fprintf( 746 stderr, "Error opening file '%s': %s\n", 747 argv[0], load_err 748 ); 749 window_show_message_fmt( 750 view->window, "Error opening file '%s': %s", 751 argv[0], load_err 752 ); 753 error = 1; 754 } 755 buffer->file_mtime = sb.st_mtim; 756 } 757 if (!error) { 758 buffer->filename = ledit_strdup(argv[0]); 759 if (!show_error) { 760 if (newfile) { 761 window_show_message_fmt(view->window, "%s: new file", argv[0]); 762 } else if (readonly) { 763 window_show_message_fmt(view->window, "%s: readonly", argv[0]); 764 } else { 765 window_show_message(view->window, argv[0], -1); 766 } 767 } 768 } 769 } 770 771 redraw(); 772 } 773 774 /* FIXME: maybe also write diagnostic information, e.g. number of lines and metadata (line length, etc.)? */ 775 void 776 ledit_emergencydump(const char *filename, int line, const char *func, const char *failedexpr) { 777 /* FIXME: pre-allocate memory for template to avoid memory errors? 778 -> probably overkill since something else will fail anyways */ 779 if (!buffer) 780 return; 781 /* FIXME: maybe write assertion message to file? */ 782 char *orig = buffer->filename ? buffer->filename : "ledit"; 783 char *suffix = "-emergency-dump-XXXXXXXXXX"; 784 size_t len1, len2; 785 len1 = strlen(orig); 786 len2 = strlen(suffix); 787 /* This doesn't use ledit_strcat so a memory allocation 788 failure doesn't interfere with the abort in the assertion 789 that calls this function. */ 790 char *template = malloc(len1 + len2 + 1); 791 /* FIXME: print error here */ 792 if (!template) 793 return; 794 strcpy(template, orig); 795 strcpy(template + len1, suffix); 796 int fd = mkstemp(template); 797 if (fd == -1) { 798 fprintf( 799 stderr, 800 "Unable to open file for emergency dump: %s\n", 801 strerror(errno) 802 ); 803 goto error; 804 } 805 char *errstr; 806 /* buffer_write_to_fd closes the file descriptor, so this has to be done */ 807 int dupfd = dup(fd); 808 /* FIXME: improve error messages here; maybe only try to write error message if file written? */ 809 if (buffer_write_to_fd(buffer, fd, &errstr)) { 810 fprintf( 811 stderr, 812 "Unable to perform emergency dump: %s\n", 813 errstr 814 ); 815 } else { 816 fprintf( 817 stderr, 818 "Wrote emergency dump to %s\n", 819 template 820 ); 821 } 822 if (dupfd == -1) { 823 fprintf( 824 stderr, 825 "Unable to duplicate file descriptor for emergency dump to write error message: %s\n", 826 strerror(errno) 827 ); 828 goto error; 829 } 830 FILE *file = fdopen(dupfd, "w"); 831 if (!file) { 832 fprintf( 833 stderr, 834 "Unable to fdopen file descriptor for emergency dump to write error message: %s\n", 835 strerror(errno) 836 ); 837 if (close(dupfd)) { 838 fprintf( 839 stderr, 840 "Unable to close duplicated file descriptor in emergency dump: %s\n", 841 strerror(errno) 842 ); 843 } 844 goto error; 845 } 846 fprintf( 847 file, 848 "ERROR MESSAGE:\n\"%s\" in \"%s\", line %d, function \"%s\"\n", 849 failedexpr, filename, line, func 850 ); 851 if (fclose(file)) 852 fprintf(stderr, "Unable to close file for emergency dump: %s\n", strerror(errno)); 853 error: 854 free(template); 855 return; 856 } 857 858 void 859 ledit_cleanup(void) { 860 search_cleanup(); 861 basic_key_cleanup(); 862 command_key_cleanup(); 863 key_processing_cleanup(); 864 if (clipboard) 865 clipboard_destroy(clipboard); 866 if (buffer) 867 buffer_destroy(buffer); 868 config_cleanup(&common); 869 XCloseDisplay(common.dpy); 870 } 871 872 static void 873 redraw(void) { 874 for (size_t i = 0; i < buffer->views_num; i++) { 875 view_redraw(buffer->views[i], cur_lang); 876 } 877 } 878 879 static void 880 change_keyboard(char *lang) { 881 ledit_debug_fmt("New keyboard layout: %s\n", lang); 882 if (config_get_language_index(lang, &cur_lang)) { 883 for (size_t i = 0; i < buffer->views_num; i++) { 884 window_show_message_fmt( 885 buffer->views[i]->window, 886 "No mapping for language \"%s\", using default mapping", 887 lang 888 ); 889 } 890 cur_lang = 0; 891 } 892 } 893 894 static void 895 key_press(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int n) { 896 /* FIXME: just let view handle this since the action is part 897 of it anyways now */ 898 if (view->cur_action.type == ACTION_GRABKEY && view->cur_action.callback) { 899 view->cur_action = view->cur_action.callback(view, key_state, sym, buf, n, cur_lang); 900 } else { 901 view->cur_action = basic_key_handler(view, key_state, sym, buf, n, cur_lang); 902 } 903 } 904 905 static void 906 key_press_event(ledit_view *view, XEvent *event) { 907 char *buf = NULL; 908 KeySym sym = NoSymbol; 909 int n; 910 unsigned int key_state = event->xkey.state; 911 preprocess_key(view->window, &event->xkey, &sym, &buf, &n); 912 key_press(view, key_state, sym, buf, n); 913 } 914 915 int 916 main(int argc, char *argv[]) { 917 setup(argc, argv); 918 mainloop(); 919 ledit_cleanup(); 920 921 return 0; 922 }