URI: 
       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 }