ledit.c - 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/… | |
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 wo… | |
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 a… | |
8 /* FIXME: horizontal scrolling (also need cache to avoid too large pixma… | |
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 s… | |
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 other… | |
70 static int | |
71 read_input(void) { | |
72 if (test_status.read_cur > 0) { | |
73 memmove(test_status.read, test_status.read + test_status… | |
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… | |
79 test_status.read = ledit_realloc(test_status.read, test_status.r… | |
80 nread = read(fileno(stdin), test_status.read + test_status.read_… | |
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.… | |
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.… | |
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.… | |
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.… | |
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 - … | |
171 } | |
172 } else { | |
173 test_status.read[test_status.read_cur - offset] … | |
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 oth… | |
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; … | |
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)te… | |
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, .b… | |
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, … | |
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, .… | |
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\… | |
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, &errs… | |
297 fprintf(stderr, "Error writing %s: %s\n"… | |
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\… | |
309 } else { | |
310 fprintf( | |
311 file, | |
312 "cursor_line: %zu, cursor_byte: %zu,… | |
313 "sel_line1: %zu, sel_byte1: %zu, " | |
314 "sel_line2: %zu, sel_byte2: %zu\n", | |
315 view->cur_line, view->cur_index, vie… | |
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\… | |
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 stdi… | |
358 return; | |
359 } | |
360 if (fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK)) { | |
361 fprintf(stderr, "Unable to set non-blocking mode on stdi… | |
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, &ma… | |
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 chang… | |
375 * When e.g. 'setxkbmap us' is executed, two events are sent, bu… | |
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… | |
398 necessary to avoid a crash when closing a view (I'm n… | |
399 entirely sure what exactly causes the crash) | |
400 -> Update: The cause of the crash was something diffe… | |
401 but I'm still leaving it as is for now because the… | |
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… | |
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 ac… | |
432 for (size_t i = 0; i < buffer->views_num; i++) { | |
433 if (buffer->views[i]->window->xwin == ev… | |
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, &ev… | |
447 break; | |
448 case ButtonPress: | |
449 if (!test_extra) | |
450 window_register_button_press(vie… | |
451 break; | |
452 case ButtonRelease: | |
453 if (!test_extra) | |
454 window_button_release(view->wind… | |
455 break; | |
456 case MotionNotify: | |
457 if (!test_extra) | |
458 window_register_motion(window, &… | |
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] == vie… | |
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.… | |
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]->… | |
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, XkbGroupNam… | |
497 /* FIXME: maybe show this as error messa… | |
498 fprintf( | |
499 stderr, | |
500 "Unable to obtain keyboard layou… | |
501 ); | |
502 if (desc) | |
503 XkbFreeClientMap(desc, 0, True); | |
504 } else { | |
505 XkbStateRec s; | |
506 XkbGetState(common.dpy, XkbUseCoreKbd, &… | |
507 char *group = XGetAtomName( | |
508 common.dpy, desc->names->groups[s.gr… | |
509 ); | |
510 change_keyboard(group); | |
511 XFree(group); | |
512 XkbFreeNames(desc, XkbGroupNamesMask, Tr… | |
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]… | |
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 bufferi… | |
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… | |
615 opening the files. This is mainly important when checking if … | |
616 file because that is not an error for functions like fopen, s… | |
617 if a non-regular file (e.g. a directory) is given to one of t… | |
618 functions. However, I don't know of any portable way to have … | |
619 check that, so this is the best I can do. */ | |
620 char *stat_errstr = NULL, *load_errstr = NULL, *load_default_err… | |
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 … | |
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 … | |
635 } | |
636 } else if (!S_ISREG(cfgst.st_mode)) { | |
637 stat_errstr = ledit_strdup("Unable to lo… | |
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 configur… | |
646 } else if (!S_ISREG(cfgst.st_mode)) { | |
647 stat_errstr = print_fmt("Unable to load configur… | |
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", c… | |
657 int failure = 1; | |
658 if (cfgfile) { | |
659 /* retry with default config */ | |
660 failure = config_loadfile(&common, NULL, &load_d… | |
661 } | |
662 if (failure) { | |
663 if (load_default_errstr) { | |
664 fprintf(stderr, "%s\n", load_default_err… | |
665 fprintf(stderr, "Also unable to load def… | |
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 … | |
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, -… | |
697 else if (load_errstr) | |
698 window_show_message(view->window, load_errstr, -… | |
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… | |
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.)… | |
715 all this checking needs to be moved to a place where … | |
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… | |
725 argv[0], strerror(errno) | |
726 ); | |
727 window_show_message_fmt( | |
728 view->window, "Error opening file '%… | |
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… | |
735 window_show_message_fmt( | |
736 view->window, "Error opening file '%s': Is n… | |
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_e… | |
745 fprintf( | |
746 stderr, "Error opening file '%s': %s… | |
747 argv[0], load_err | |
748 ); | |
749 window_show_message_fmt( | |
750 view->window, "Error opening file '%… | |
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->wi… | |
762 } else if (readonly) { | |
763 window_show_message_fmt(view->wi… | |
764 } else { | |
765 window_show_message(view->window… | |
766 } | |
767 } | |
768 } | |
769 } | |
770 | |
771 redraw(); | |
772 } | |
773 | |
774 /* FIXME: maybe also write diagnostic information, e.g. number of lines … | |
775 void | |
776 ledit_emergencydump(const char *filename, int line, const char *func, co… | |
777 /* FIXME: pre-allocate memory for template to avoid memory error… | |
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… | |
807 int dupfd = dup(fd); | |
808 /* FIXME: improve error messages here; maybe only try to write e… | |
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 d… | |
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… | |
835 strerror(errno) | |
836 ); | |
837 if (close(dupfd)) { | |
838 fprintf( | |
839 stderr, | |
840 "Unable to close duplicated file descriptor … | |
841 strerror(errno) | |
842 ); | |
843 } | |
844 goto error; | |
845 } | |
846 fprintf( | |
847 file, | |
848 "ERROR MESSAGE:\n\"%s\" in \"%s\", line %d, function \"%s\"\… | |
849 failedexpr, filename, line, func | |
850 ); | |
851 if (fclose(file)) | |
852 fprintf(stderr, "Unable to close file for emergency dump… | |
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 defau… | |
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 *bu… | |
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.… | |
899 view->cur_action = view->cur_action.callback(view, key_s… | |
900 } else { | |
901 view->cur_action = basic_key_handler(view, key_state, sy… | |
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 } |