ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/ledit.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
Log | Files | Refs | README | LICENSE

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 }