ledit.c - ledit - Text editor (WIP)
HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
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 }