configparser.c (63996B)
1 #ifdef LEDIT_DEBUG 2 #include <time.h> 3 #include "macros.h" 4 #endif 5 #include <stdio.h> 6 #include <ctype.h> 7 #include <errno.h> 8 #include <string.h> 9 #include <stdint.h> 10 #include <stdlib.h> 11 #include <limits.h> 12 13 #include "util.h" 14 #include "memory.h" 15 #include "assert.h" 16 #include "configparser.h" 17 #include "theme_config.h" 18 #include "keys_config.h" 19 20 /* FIXME: Replace this entire parser with something sensible. 21 The current handwritten parser is mainly for the lulz. */ 22 23 /* FIXME: standardize error messages */ 24 /* FIXME: it isn't entirely correct to give size_t as length for 25 string in print_fmt (supposed to be int) */ 26 27 struct config { 28 ledit_theme *theme; 29 basic_key_array *basic_keys; 30 command_key_array *command_keys; 31 command_array *cmds; 32 char **langs; 33 size_t num_langs; 34 size_t alloc_langs; 35 } config = {NULL, NULL, NULL, NULL, NULL, 0, 0}; 36 37 enum toktype { 38 STRING, 39 LBRACE, 40 RBRACE, 41 EQUALS, 42 NEWLINE, 43 ERROR, 44 END 45 }; 46 47 static const char * 48 toktype_str(enum toktype type) { 49 switch (type) { 50 case STRING: 51 return "string"; 52 break; 53 case LBRACE: 54 return "left brace"; 55 break; 56 case RBRACE: 57 return "right brace"; 58 break; 59 case EQUALS: 60 return "equals"; 61 break; 62 case NEWLINE: 63 return "newline"; 64 break; 65 case ERROR: 66 return "error"; 67 break; 68 case END: 69 return "end of file"; 70 break; 71 default: 72 return "unknown"; 73 } 74 } 75 76 struct token { 77 char *text; 78 size_t len; 79 enum toktype type; 80 size_t line; /* line in original input */ 81 size_t line_offset; /* offset from start of line */ 82 }; 83 84 struct lexstate { 85 char *text; 86 size_t len; /* length of text */ 87 size_t cur; /* current byte position */ 88 size_t cur_line; /* current line */ 89 size_t line_start; /* byte offset of start of current line */ 90 }; 91 92 static struct token 93 next_token(struct lexstate *s) { 94 char c; 95 struct token tok; 96 while (1) { 97 if (s->cur >= s->len) 98 return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; 99 while (isspace(c = s->text[s->cur])) { 100 s->cur++; 101 if (c == '\n') { 102 struct token tok = (struct token){s->text + s->cur - 1, 1, NEWLINE, s->cur_line, s->cur - s->line_start}; 103 s->cur_line++; 104 s->line_start = s->cur; 105 return tok; 106 } 107 if (s->cur >= s->len) 108 return (struct token){NULL, 0, END, s->cur_line, s->cur - s->line_start + 1}; 109 } 110 111 switch (s->text[s->cur]) { 112 case '#': 113 s->cur++; 114 while (s->cur < s->len && s->text[s->cur] != '\n') 115 s->cur++; 116 continue; 117 case '{': 118 tok = (struct token){s->text + s->cur, 1, LBRACE, s->cur_line, s->cur - s->line_start + 1}; 119 s->cur++; 120 break; 121 case '}': 122 tok = (struct token){s->text + s->cur, 1, RBRACE, s->cur_line, s->cur - s->line_start + 1}; 123 s->cur++; 124 break; 125 case '=': 126 tok = (struct token){s->text + s->cur, 1, EQUALS, s->cur_line, s->cur - s->line_start + 1}; 127 s->cur++; 128 break; 129 case '"': 130 /* FIXME: error if next char is not whitespace or end */ 131 s->cur++; 132 tok = (struct token){s->text + s->cur, 0, STRING, s->cur_line, s->cur - s->line_start + 1}; 133 size_t shift = 0, bs = 0; 134 int finished = 0; 135 while (s->cur < s->len) { 136 char c = s->text[s->cur]; 137 if (c == '\n') { 138 break; 139 } else if (c == '\\') { 140 shift += bs; 141 tok.len += bs; 142 bs = (bs + 1) % 2; 143 } else if (c == '"') { 144 if (bs) { 145 shift++; 146 tok.len++; 147 bs = 0; 148 } else { 149 s->cur++; 150 finished = 1; 151 break; 152 } 153 } else { 154 tok.len++; 155 } 156 s->text[s->cur - shift] = s->text[s->cur]; 157 s->cur++; 158 } 159 if (!finished) { 160 tok.text = "Unfinished string"; 161 tok.len = strlen("Unfinished string"); 162 tok.type = ERROR; 163 } 164 break; 165 default: 166 tok = (struct token){s->text + s->cur, 1, STRING, s->cur_line, s->cur - s->line_start + 1}; 167 s->cur++; 168 while (s->cur < s->len) { 169 char c = s->text[s->cur]; 170 if (isspace(c) || c == '{' || c == '}' || c == '=') { 171 break; 172 } else if (c == '"') { 173 tok.text = "Unexpected start of string"; 174 tok.len = strlen("Unexpected start of string"); 175 tok.type = ERROR; 176 tok.line_offset = s->cur - s->line_start + 1; 177 } 178 tok.len++; 179 s->cur++; 180 } 181 } 182 return tok; 183 } 184 } 185 186 typedef struct ast_obj ast_obj; 187 188 typedef struct { 189 ast_obj *objs; 190 size_t len, cap; 191 } ast_list; 192 193 typedef struct { 194 struct token tok; 195 } ast_string; 196 197 typedef struct { 198 struct token tok; 199 ast_obj *value; 200 } ast_assignment; 201 202 typedef struct { 203 struct token func_tok; 204 struct token *args; 205 size_t len, cap; 206 } ast_statement; 207 208 enum objtype { 209 OBJ_LIST, 210 OBJ_STRING, 211 OBJ_ASSIGNMENT, 212 OBJ_STATEMENT 213 }; 214 215 struct ast_obj { 216 struct token tok; 217 union { 218 ast_list list; 219 ast_string str; 220 ast_assignment assignment; 221 ast_statement statement; 222 } obj; 223 enum objtype type; 224 }; 225 226 /* Note: These functions only free everything inside the object 227 so they can be used with stack variables (or array elements)! */ 228 229 static void destroy_obj(ast_obj *obj); 230 231 static void 232 destroy_list(ast_list *list) { 233 if (!list) 234 return; 235 for (size_t i = 0; i < list->len; i++) { 236 destroy_obj(&list->objs[i]); 237 } 238 free(list->objs); 239 list->objs = NULL; 240 list->len = list->cap = 0; 241 } 242 243 static void 244 destroy_obj(ast_obj *obj) { 245 if (!obj) 246 return; 247 switch (obj->type) { 248 case OBJ_LIST: 249 destroy_list(&obj->obj.list); 250 break; 251 case OBJ_ASSIGNMENT: 252 destroy_obj(obj->obj.assignment.value); 253 free(obj->obj.assignment.value); 254 obj->obj.assignment.value = NULL; 255 break; 256 case OBJ_STATEMENT: 257 free(obj->obj.statement.args); 258 obj->obj.statement.args = NULL; 259 obj->obj.statement.len = obj->obj.statement.cap = 0; 260 break; 261 default: 262 break; 263 } 264 } 265 266 /* FIXME: overflow */ 267 static void 268 list_append(ast_list *list, ast_obj o) { 269 list->cap = ideal_array_size(list->cap, add_sz(list->len, 1)); 270 list->objs = ledit_reallocarray(list->objs, list->cap, sizeof(ast_obj)); 271 list->objs[list->len++] = o; 272 } 273 274 static void 275 statement_append(ast_statement *statement, struct token tok) { 276 statement->cap = ideal_array_size(statement->cap, add_sz(statement->len, 1)); 277 statement->args = ledit_reallocarray(statement->args, statement->cap, sizeof(struct token)); 278 statement->args[statement->len++] = tok; 279 } 280 281 /* FIXME: make this a bit nicer */ 282 /* Note: A lot of the ugliness is because of the 283 (failed) attempt to somewhat optimize everything */ 284 285 static int 286 parse_list(struct lexstate *s, ast_list *ret, int implicit_end, char *filename, char **errstr) { 287 *ret = (ast_list){NULL, 0, 0}; 288 struct token tok = next_token(s); 289 struct token tok2; 290 while (1) { 291 switch (tok.type) { 292 case STRING: 293 tok2 = next_token(s); 294 if (tok2.type == STRING) { 295 ast_statement statement = {tok, NULL, 0, 0}; 296 /* FIXME: maybe allow lists in statements? */ 297 while (tok2.type == STRING) { 298 statement_append(&statement, tok2); 299 tok2 = next_token(s); 300 } 301 list_append(ret, (ast_obj){.tok = tok, .obj = {.statement = statement}, .type = OBJ_STATEMENT}); 302 tok = tok2; 303 } else if (tok2.type == EQUALS) { 304 ast_assignment assignment = {tok, NULL}; 305 assignment.value = ledit_malloc(sizeof(ast_obj)); 306 tok2 = next_token(s); 307 assignment.value->tok = tok2; 308 struct token orig_tok = tok; 309 if (tok2.type == STRING) { 310 assignment.value->obj.str = (ast_string){tok2}; 311 assignment.value->type = OBJ_STRING; 312 tok = next_token(s); 313 if (tok.type == STRING) { 314 *errstr = print_fmt( 315 "%s: Invalid assignment at line %zu, offset %zu", 316 filename, tok.line, tok.line_offset 317 ); 318 free(assignment.value); 319 goto error; 320 } 321 } else if (tok2.type == LBRACE) { 322 assignment.value->type = OBJ_LIST; 323 /* just in case */ 324 assignment.value->obj.list = (ast_list){NULL, 0, 0}; 325 if (parse_list(s, &assignment.value->obj.list, 0, filename, errstr)) { 326 free(assignment.value); 327 goto error; 328 } 329 tok = next_token(s); 330 if (tok.type == STRING) { 331 *errstr = print_fmt( 332 "%s: Invalid assignment at line %zu, offset %zu", 333 filename, tok.line, tok.line_offset 334 ); 335 destroy_list(&assignment.value->obj.list); 336 free(assignment.value); 337 goto error; 338 } 339 } else { 340 *errstr = print_fmt( 341 "%s: Invalid assignment at line %zu, offset %zu", 342 filename, tok2.line, tok2.line_offset 343 ); 344 free(assignment.value); 345 goto error; 346 } 347 list_append(ret, (ast_obj){.tok = orig_tok, .obj = {.assignment = assignment}, .type = OBJ_ASSIGNMENT}); 348 } else { 349 *errstr = print_fmt( 350 "%s: Invalid token '%s' at line %zu, offset %zu", 351 filename, toktype_str(tok2.type), tok2.line, tok2.line_offset 352 ); 353 goto error; 354 } 355 break; 356 case NEWLINE: 357 tok = next_token(s); 358 break; 359 case RBRACE: 360 if (implicit_end) { 361 *errstr = print_fmt( 362 "%s: Unexpected right brace at line %zu, offset %zu", 363 filename, tok.line, tok.line_offset 364 ); 365 goto error; 366 } else { 367 return 0; 368 } 369 case END: 370 if (!implicit_end) { 371 *errstr = print_fmt( 372 "%s: Unexpected end of file at line %zu, offset %zu", 373 filename, tok.line, tok.line_offset 374 ); 375 goto error; 376 } else { 377 return 0; 378 } 379 case LBRACE: 380 case EQUALS: 381 case ERROR: 382 default: 383 *errstr = print_fmt( 384 "%s: Unexpected token '%s' at line %zu, offset %zu", 385 filename, toktype_str(tok.type), tok.line, tok.line_offset 386 ); 387 goto error; 388 } 389 } 390 return 0; 391 error: 392 destroy_list(ret); 393 return 1; 394 } 395 396 static char * 397 load_file(char *filename, size_t *len_ret, char **errstr) { 398 long len; 399 char *file_contents; 400 FILE *file; 401 402 /* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */ 403 file = fopen(filename, "r"); 404 if (!file) goto error; 405 if (fseek(file, 0, SEEK_END)) goto errorclose; 406 len = ftell(file); 407 if (len < 0) goto errorclose; 408 if (fseek(file, 0, SEEK_SET)) goto errorclose; 409 file_contents = ledit_malloc(add_sz((size_t)len, 1)); 410 clearerr(file); 411 fread(file_contents, 1, (size_t)len, file); 412 if (ferror(file)) goto errorclose; 413 file_contents[len] = '\0'; 414 if (fclose(file)) goto error; 415 *len_ret = (size_t)len; 416 return file_contents; 417 error: 418 if (errstr) 419 *errstr = strerror(errno); 420 return NULL; 421 errorclose: 422 if (errstr) 423 *errstr = strerror(errno); 424 fclose(file); 425 return NULL; 426 } 427 428 /* FIXME: max recursion depth in parser */ 429 430 static int 431 parse_theme_color( 432 ledit_common *common, 433 void *obj, const char *val, size_t val_len, char *key, 434 char *filename, size_t line, size_t line_offset, char **errstr) { 435 XftColor *dst = (XftColor *)obj; 436 char col[8]; /* 7 for '#' and 6 hex values + 1 for '\0' */ 437 if (val_len == 7 && val[0] == '#') { 438 strncpy(col, val, val_len); 439 col[val_len] = '\0'; 440 } else if (val_len == 6) { 441 col[0] = '#'; 442 strncpy(col + 1, val, val_len); 443 col[val_len + 1] = '\0'; 444 } else { 445 goto error; 446 } 447 /* FIXME: XftColorAllocValue */ 448 if (!XftColorAllocName(common->dpy, common->vis, common->cm, col, dst)) 449 goto error; 450 return 0; 451 error: 452 *errstr = print_fmt( 453 "%s: Unable to parse color specification " 454 "'%.*s' for '%s' at line %zu, position %zu", 455 filename, val_len, val, key, line, line_offset 456 ); 457 return 1; 458 } 459 460 static void 461 destroy_theme_color(ledit_common *common, void *obj) { 462 XftColor *color = (XftColor *)obj; 463 XftColorFree(common->dpy, common->vis, common->cm, color); 464 } 465 466 /* based partially on OpenBSD's strtonum */ 467 static int 468 parse_theme_number( 469 ledit_common *common, 470 void *obj, const char *val, size_t val_len, char *key, 471 char *filename, size_t line, size_t line_offset, char **errstr) { 472 (void)common; 473 int *num = (int *)obj; 474 /* the string needs to be nul-terminated 475 if it contains more than 9 digits, it's illegal anyways */ 476 if (val_len > 9) 477 goto error; 478 char str[10]; 479 strncpy(str, val, val_len); 480 str[val_len] = '\0'; 481 char *end; 482 long l = strtol(str, &end, 10); 483 if (str == end || *end != '\0' || 484 l < 0 || l > INT_MAX || ((l == LONG_MIN || 485 l == LONG_MAX) && errno == ERANGE)) { 486 goto error; 487 } 488 *num = (int)l; 489 return 0; 490 error: 491 *errstr = print_fmt( 492 "%s: Invalid number '%.*s' " 493 "for '%s' at line %zu, position %zu", 494 filename, val_len, val, key, line, line_offset 495 ); 496 return 1; 497 } 498 499 static void 500 destroy_theme_number(ledit_common *common, void *obj) { 501 (void)common; 502 (void)obj; 503 } 504 505 static int 506 parse_theme_string( 507 ledit_common *common, 508 void *obj, const char *val, size_t val_len, char *key, 509 char *filename, size_t line, size_t line_offset, char **errstr) { 510 (void)common; (void)key; 511 (void)filename; (void)line; (void)line_offset; (void)errstr; 512 513 char **obj_str = (char **)obj; 514 *obj_str = ledit_strndup(val, val_len); 515 return 0; 516 } 517 518 static void 519 destroy_theme_string(ledit_common *common, void *obj) { 520 (void)common; 521 char **obj_str = (char **)obj; 522 free(*obj_str); 523 } 524 525 static int 526 parse_theme_bool( 527 ledit_common *common, 528 void *obj, const char *val, size_t val_len, char *key, 529 char *filename, size_t line, size_t line_offset, char **errstr) { 530 (void)common; 531 int *num = (int *)obj; 532 if (str_array_equal("true", val, val_len)) { 533 *num = 1; 534 return 0; 535 } else if (str_array_equal("false", val, val_len)) { 536 *num = 0; 537 return 0; 538 } 539 *errstr = print_fmt( 540 "%s: Invalid boolean '%.*s' " 541 "for '%s' at line %zu, position %zu", 542 filename, val_len, val, key, line, line_offset 543 ); 544 return 1; 545 } 546 547 static void 548 destroy_theme_bool(ledit_common *common, void *obj) { 549 (void)common; 550 (void)obj; 551 } 552 553 /* FIXME: This interface is absolutely horrible - it's mainly this way to reuse the 554 theme array for the destroy function */ 555 /* If theme is NULL, a new theme is loaded, else it is destroyed */ 556 static ledit_theme * 557 load_destroy_theme(ledit_common *common, ast_list *theme_list, ledit_theme *theme, char *filename, char **errstr) { 558 *errstr = NULL; 559 int default_init = theme ? 1 : 0; 560 if (!theme) 561 theme = ledit_malloc(sizeof(ledit_theme)); 562 563 struct { 564 char *key; 565 void *obj; 566 int (*parse_func)( 567 ledit_common *common, 568 void *obj, const char *val, size_t val_len, char *key, 569 char *filename, size_t line, size_t line_offset, char **errstr 570 ); 571 void (*destroy_func)(ledit_common *common, void *obj); 572 const char *default_value; 573 int initialized; 574 } settings[] = { 575 {"text-font", &theme->text_font, &parse_theme_string, &destroy_theme_string, TEXT_FONT, default_init}, 576 {"text-size", &theme->text_size, &parse_theme_number, &destroy_theme_number, TEXT_SIZE, default_init}, 577 {"scrollbar-width", &theme->scrollbar_width, &parse_theme_number, &destroy_theme_number, SCROLLBAR_WIDTH, default_init}, 578 {"scrollbar-step", &theme->scrollbar_step, &parse_theme_number, &destroy_theme_number, SCROLLBAR_STEP, default_init}, 579 {"extra-line-spacing", &theme->extra_line_spacing, &parse_theme_number, &destroy_theme_number, EXTRA_LINE_SPACING, default_init}, 580 {"text-fg", &theme->text_fg, &parse_theme_color, &destroy_theme_color, TEXT_FG, default_init}, 581 {"text-bg", &theme->text_bg, &parse_theme_color, &destroy_theme_color, TEXT_BG, default_init}, 582 {"cursor-fg", &theme->cursor_fg, &parse_theme_color, &destroy_theme_color, CURSOR_FG, default_init}, 583 {"cursor-bg", &theme->cursor_bg, &parse_theme_color, &destroy_theme_color, CURSOR_BG, default_init}, 584 {"selection-fg", &theme->selection_fg, &parse_theme_color, &destroy_theme_color, SELECTION_FG, default_init}, 585 {"selection-bg", &theme->selection_bg, &parse_theme_color, &destroy_theme_color, SELECTION_BG, default_init}, 586 {"bar-fg", &theme->bar_fg, &parse_theme_color, &destroy_theme_color, BAR_FG, default_init}, 587 {"bar-bg", &theme->bar_bg, &parse_theme_color, &destroy_theme_color, BAR_BG, default_init}, 588 {"bar-cursor", &theme->bar_cursor, &parse_theme_color, &destroy_theme_color, BAR_CURSOR, default_init}, 589 {"bar-fmt", &theme->bar_fmt, &parse_theme_string, &destroy_theme_string, BAR_FMT, default_init}, 590 {"scrollbar-fg", &theme->scrollbar_fg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_FG, default_init}, 591 {"scrollbar-bg", &theme->scrollbar_bg, &parse_theme_color, &destroy_theme_color, SCROLLBAR_BG, default_init}, 592 {"highlight-search", &theme->highlight_search, &parse_theme_bool, &destroy_theme_bool, HIGHLIGHT_SEARCH, default_init}, 593 }; 594 595 if (default_init) 596 goto cleanup; 597 598 if (theme_list) { 599 for (size_t i = 0; i < theme_list->len; i++) { 600 size_t line = theme_list->objs[i].tok.line; 601 size_t line_offset = theme_list->objs[i].tok.line_offset; 602 if (theme_list->objs[i].type != OBJ_ASSIGNMENT) { 603 *errstr = print_fmt( 604 "%s: Invalid statement in theme configuration " 605 "at line %zu, offset %zu", filename, line, line_offset 606 ); 607 goto cleanup; 608 } else if (theme_list->objs[i].obj.assignment.value->type != OBJ_STRING) { 609 *errstr = print_fmt( 610 "%s: Invalid assignment in theme configuration " 611 "at line %zu, offset %zu", filename, line, line_offset 612 ); 613 goto cleanup; 614 } 615 616 char *key = theme_list->objs[i].obj.assignment.tok.text; 617 size_t key_len = theme_list->objs[i].obj.assignment.tok.len; 618 char *val = theme_list->objs[i].obj.assignment.value->obj.str.tok.text; 619 size_t val_len = theme_list->objs[i].obj.assignment.value->obj.str.tok.len; 620 621 int found = 0; 622 /* FIXME: use binary search maybe */ 623 for (size_t j = 0; j < LENGTH(settings); j++) { 624 if (str_array_equal(settings[j].key, key, key_len)) { 625 /* FIXME: maybe just make this a warning? */ 626 if (settings[j].initialized) { 627 *errstr = print_fmt( 628 "%s: Duplicate definition of " 629 "'%.*s' at line %zu, position %zu", 630 filename, key_len, key, line, line_offset 631 ); 632 goto cleanup; 633 } 634 if (settings[j].parse_func( 635 common, settings[j].obj, val, val_len, 636 settings[j].key, filename, line, line_offset, errstr)) { 637 goto cleanup; 638 } 639 settings[j].initialized = 1; 640 found = 1; 641 break; 642 } 643 } 644 if (!found) { 645 *errstr = print_fmt( 646 "%s: Invalid theme setting " 647 "'%.*s' at line %zu, position %zu", 648 filename, key_len, key, line, line_offset 649 ); 650 goto cleanup; 651 } 652 } 653 } 654 655 for (size_t i = 0; i < LENGTH(settings); i++) { 656 if (!settings[i].initialized) { 657 /* FIXME: kind of inefficient to calculate strlen at runtime */ 658 /* FIXME: line number doesn't make sense */ 659 if (settings[i].parse_func( 660 common, settings[i].obj, settings[i].default_value, 661 strlen(settings[i].default_value), settings[i].key, 662 "default config", 0, 0, errstr)) { 663 goto cleanup; 664 } 665 } 666 } 667 668 /* FIXME: make this check part of the generic handling above (also, < 0 is already checked anyways) */ 669 /* FIXME: 100 is completely arbitrary */ 670 if (theme->extra_line_spacing < 0 || theme->extra_line_spacing > 100) { 671 *errstr = print_fmt( 672 "%s: Invalid value '%d' for theme setting 'extra-line-spacing' " 673 "(allowed values are 0-100)", 674 filename, theme->extra_line_spacing 675 ); 676 goto cleanup; 677 } 678 679 return theme; 680 cleanup: 681 for (size_t i = 0; i < LENGTH(settings); i++) { 682 if (settings[i].initialized) { 683 settings[i].destroy_func(common, settings[i].obj); 684 } 685 } 686 free(theme); 687 return NULL; 688 } 689 690 static ledit_theme * 691 load_theme(ledit_common *common, ast_list *theme_list, char *filename, char **errstr) { 692 return load_destroy_theme(common, theme_list, NULL, filename, errstr); 693 } 694 695 static void 696 destroy_theme(ledit_common *common, ledit_theme *theme) { 697 char *errstr = NULL; 698 if (!theme) 699 return; 700 (void)load_destroy_theme(common, NULL, theme, NULL, &errstr); 701 /* shouldn't happen... */ 702 if (errstr) 703 free(errstr); 704 } 705 706 /* This only destroys the members inside 'cfg' since the config 707 * struct itself is usually not on the heap. */ 708 static void 709 config_destroy(ledit_common *common, struct config *cfg) { 710 if (cfg->theme) 711 destroy_theme(common, cfg->theme); 712 cfg->theme = NULL; 713 for (size_t i = 0; i < cfg->num_langs; i++) { 714 for (size_t j = 0; j < cfg->basic_keys[i].num_keys; j++) { 715 free(cfg->basic_keys[i].keys[j].text); 716 } 717 free(cfg->basic_keys[i].keys); 718 for (size_t j = 0; j < cfg->command_keys[i].num_keys; j++) { 719 free(cfg->command_keys[i].keys[j].text); 720 } 721 free(cfg->command_keys[i].keys); 722 for (size_t j = 0; j < cfg->cmds[i].num_cmds; j++) { 723 free(cfg->cmds[i].cmds[j].text); 724 } 725 free(cfg->cmds[i].cmds); 726 free(cfg->langs[i]); 727 } 728 free(cfg->basic_keys); 729 free(cfg->command_keys); 730 free(cfg->cmds); 731 free(cfg->langs); 732 cfg->basic_keys = NULL; 733 cfg->command_keys = NULL; 734 cfg->cmds = NULL; 735 cfg->langs = NULL; 736 cfg->num_langs = cfg->alloc_langs = 0; 737 } 738 739 void 740 config_cleanup(ledit_common *common) { 741 config_destroy(common, &config); 742 } 743 744 /* FIXME: which additional ones are needed here? */ 745 static struct keysym_mapping { 746 char *name; 747 KeySym keysym; 748 } keysym_map[] = { 749 {"backspace", XK_BackSpace}, 750 {"begin", XK_Begin}, 751 {"break", XK_Break}, 752 {"cancel", XK_Cancel}, 753 {"clear", XK_Clear}, 754 {"delete", XK_Delete}, 755 {"down", XK_Down}, 756 {"end", XK_End}, 757 {"escape", XK_Escape}, 758 {"execute", XK_Execute}, 759 760 {"f1", XK_F1}, 761 {"f10", XK_F10}, 762 {"f11", XK_F11}, 763 {"f12", XK_F12}, 764 {"f13", XK_F13}, 765 {"f14", XK_F14}, 766 {"f15", XK_F15}, 767 {"f16", XK_F16}, 768 {"f17", XK_F17}, 769 {"f18", XK_F18}, 770 {"f19", XK_F19}, 771 {"f2", XK_F2}, 772 {"f20", XK_F20}, 773 {"f21", XK_F21}, 774 {"f22", XK_F22}, 775 {"f23", XK_F23}, 776 {"f24", XK_F24}, 777 {"f25", XK_F25}, 778 {"f26", XK_F26}, 779 {"f27", XK_F27}, 780 {"f28", XK_F28}, 781 {"f29", XK_F29}, 782 {"f3", XK_F3}, 783 {"f30", XK_F30}, 784 {"f31", XK_F31}, 785 {"f32", XK_F32}, 786 {"f33", XK_F33}, 787 {"f34", XK_F34}, 788 {"f35", XK_F35}, 789 {"f4", XK_F4}, 790 {"f5", XK_F5}, 791 {"f6", XK_F6}, 792 {"f7", XK_F7}, 793 {"f8", XK_F8}, 794 {"f9", XK_F9}, 795 796 {"find", XK_Find}, 797 {"help", XK_Help}, 798 {"home", XK_Home}, 799 {"insert", XK_Insert}, 800 801 {"kp-0", XK_KP_0}, 802 {"kp-1", XK_KP_1}, 803 {"kp-2", XK_KP_2}, 804 {"kp-3", XK_KP_3}, 805 {"kp-4", XK_KP_4}, 806 {"kp-5", XK_KP_5}, 807 {"kp-6", XK_KP_6}, 808 {"kp-7", XK_KP_7}, 809 {"kp-8", XK_KP_8}, 810 {"kp-9", XK_KP_9}, 811 {"kp-add", XK_KP_Add}, 812 {"kp-begin", XK_KP_Begin}, 813 {"kp-decimal", XK_KP_Decimal}, 814 {"kp-delete", XK_KP_Delete}, 815 {"kp-divide", XK_KP_Divide}, 816 {"kp-down", XK_KP_Down}, 817 {"kp-end", XK_KP_End}, 818 {"kp-enter", XK_KP_Enter}, 819 {"kp-equal", XK_KP_Equal}, 820 {"kp-f1", XK_KP_F1}, 821 {"kp-f2", XK_KP_F2}, 822 {"kp-f3", XK_KP_F3}, 823 {"kp-f4", XK_KP_F4}, 824 {"kp-home", XK_KP_Home}, 825 {"kp-insert", XK_KP_Insert}, 826 {"kp-left", XK_KP_Left}, 827 {"kp-multiply", XK_KP_Multiply}, 828 {"kp-next", XK_KP_Next}, 829 {"kp-page-down", XK_KP_Page_Down}, 830 {"kp-page-up", XK_KP_Page_Up}, 831 {"kp-prior", XK_KP_Prior}, 832 {"kp-right", XK_KP_Right}, 833 {"kp-separator", XK_KP_Separator}, 834 {"kp-space", XK_KP_Space}, 835 {"kp-subtract", XK_KP_Subtract}, 836 {"kp-tab", XK_KP_Tab}, 837 {"kp-up", XK_KP_Up}, 838 839 {"l1", XK_L1}, 840 {"l10", XK_L10}, 841 {"l2", XK_L2}, 842 {"l3", XK_L3}, 843 {"l4", XK_L4}, 844 {"l5", XK_L5}, 845 {"l6", XK_L6}, 846 {"l7", XK_L7}, 847 {"l8", XK_L8}, 848 {"l9", XK_L9}, 849 850 {"left", XK_Left}, 851 {"linefeed", XK_Linefeed}, 852 {"menu", XK_Menu}, 853 {"mode-switch", XK_Mode_switch}, 854 {"next", XK_Next}, 855 {"num-lock", XK_Num_Lock}, 856 {"page-down", XK_Page_Down}, 857 {"page-up", XK_Page_Up}, 858 {"pause", XK_Pause}, 859 {"print", XK_Print}, 860 {"prior", XK_Prior}, 861 862 {"r1", XK_R1}, 863 {"r10", XK_R10}, 864 {"r11", XK_R11}, 865 {"r12", XK_R12}, 866 {"r13", XK_R13}, 867 {"r14", XK_R14}, 868 {"r15", XK_R15}, 869 {"r2", XK_R2}, 870 {"r3", XK_R3}, 871 {"r4", XK_R4}, 872 {"r5", XK_R5}, 873 {"r6", XK_R6}, 874 {"r7", XK_R7}, 875 {"r8", XK_R8}, 876 {"r9", XK_R9}, 877 878 {"redo", XK_Redo}, 879 {"return", XK_Return}, 880 {"right", XK_Right}, 881 {"script-switch", XK_script_switch}, 882 {"scroll-lock", XK_Scroll_Lock}, 883 {"select", XK_Select}, 884 {"space", XK_space}, 885 {"sysreq", XK_Sys_Req}, 886 {"tab", XK_Tab}, 887 {"up", XK_Up}, 888 {"undo", XK_Undo}, 889 }; 890 891 GEN_CB_MAP_HELPERS(keysym_map, struct keysym_mapping, name) 892 893 static int 894 parse_keysym(char *keysym_str, size_t len, KeySym *sym) { 895 struct keysym_mapping *km = keysym_map_get_entry(keysym_str, len); 896 if (!km) 897 return 1; 898 *sym = km->keysym; 899 return 0; 900 } 901 902 static int 903 parse_modemask(char *modemask_str, size_t len, ledit_mode *mode_ret) { 904 size_t cur = 0; 905 *mode_ret = 0; 906 while (cur < len) { 907 if (str_array_equal("normal", modemask_str + cur, LEDIT_MIN(6, len - cur))) { 908 cur += 6; 909 *mode_ret |= NORMAL; 910 } else if (str_array_equal("visual", modemask_str + cur, LEDIT_MIN(6, len - cur))) { 911 cur += 6; 912 *mode_ret |= VISUAL; 913 } else if (str_array_equal("insert", modemask_str + cur, LEDIT_MIN(6, len - cur))) { 914 cur += 6; 915 *mode_ret |= INSERT; 916 } else { 917 return 1; 918 } 919 if (cur < len && modemask_str[cur] != '|') 920 return 1; 921 else 922 cur++; 923 } 924 return 0; 925 } 926 927 static int 928 parse_modmask(char *modmask_str, size_t len, unsigned int *mask_ret) { 929 size_t cur = 0; 930 *mask_ret = 0; 931 while (cur < len) { 932 if (str_array_equal("shift", modmask_str + cur, LEDIT_MIN(5, len - cur))) { 933 cur += 5; 934 *mask_ret |= ShiftMask; 935 /* 936 } else if (str_array_equal("lock", modmask_str + cur, LEDIT_MIN(4, len - cur))) { 937 cur += 4; 938 *mask_ret |= LockMask; 939 */ 940 } else if (str_array_equal("control", modmask_str + cur, LEDIT_MIN(7, len - cur))) { 941 cur += 7; 942 *mask_ret |= ControlMask; 943 } else if (str_array_equal("mod1", modmask_str + cur, LEDIT_MIN(4, len - cur))) { 944 cur += 4; 945 *mask_ret |= Mod1Mask; 946 /* 947 } else if (str_array_equal("mod2", modmask_str + cur, LEDIT_MIN(4, len - cur))) { 948 cur += 4; 949 *mask_ret |= Mod2Mask; 950 */ 951 } else if (str_array_equal("mod3", modmask_str + cur, LEDIT_MIN(4, len - cur))) { 952 cur += 4; 953 *mask_ret |= Mod3Mask; 954 } else if (str_array_equal("mod4", modmask_str + cur, LEDIT_MIN(4, len - cur))) { 955 cur += 4; 956 *mask_ret |= Mod4Mask; 957 } else if (str_array_equal("mod5", modmask_str + cur, LEDIT_MIN(4, len - cur))) { 958 cur += 4; 959 *mask_ret |= Mod5Mask; 960 } else if (str_array_equal("any", modmask_str + cur, LEDIT_MIN(3, len - cur))) { 961 cur += 3; 962 *mask_ret = UINT_MAX; 963 } else { 964 return 1; 965 } 966 if (cur < len && modmask_str[cur] != '|') 967 return 1; 968 else 969 cur++; 970 } 971 return 0; 972 } 973 974 /* FIXME: it would probably be safer to not write the string lengths by hand... */ 975 static int 976 parse_command_modemask(char *mode_str, size_t len, command_mode *mode_ret) { 977 size_t cur = 0; 978 *mode_ret = 0; 979 /* IMPORTANT: these need to be sorted appropriately so e.g. edit doesn't mess with edit-search */ 980 while (cur < len) { 981 if (str_array_equal("substitute", mode_str + cur, LEDIT_MIN(10, len - cur))) { 982 cur += 10; 983 *mode_ret |= CMD_SUBSTITUTE; 984 } else if (str_array_equal("edit-search-backwards", mode_str + cur, LEDIT_MIN(21, len - cur))) { 985 cur += 21; 986 *mode_ret |= CMD_EDITSEARCHB; 987 } else if (str_array_equal("edit-search", mode_str + cur, LEDIT_MIN(11, len - cur))) { 988 cur += 11; 989 *mode_ret |= CMD_EDITSEARCH; 990 } else if (str_array_equal("edit", mode_str + cur, LEDIT_MIN(4, len - cur))) { 991 cur += 4; 992 *mode_ret |= CMD_EDIT; 993 } else { 994 return 1; 995 } 996 if (cur < len && mode_str[cur] != '|') { 997 return 1; 998 } else { 999 cur++; 1000 } 1001 } 1002 return 0; 1003 } 1004 1005 /* FIXME: generic dynamic array */ 1006 1007 static void 1008 push_lang(struct config *cfg) { 1009 if (cfg->num_langs == cfg->alloc_langs) { 1010 cfg->alloc_langs = ideal_array_size(cfg->alloc_langs, add_sz(cfg->num_langs, 1)); 1011 cfg->basic_keys = ledit_reallocarray(cfg->basic_keys, cfg->alloc_langs, sizeof(basic_key_array)); 1012 cfg->command_keys = ledit_reallocarray(cfg->command_keys, cfg->alloc_langs, sizeof(command_key_array)); 1013 cfg->cmds = ledit_reallocarray(cfg->cmds, cfg->alloc_langs, sizeof(command_array)); 1014 cfg->langs = ledit_reallocarray(cfg->langs, cfg->alloc_langs, sizeof(char *)); 1015 } 1016 basic_key_array *arr1 = &cfg->basic_keys[cfg->num_langs]; 1017 arr1->keys = NULL; 1018 arr1->num_keys = arr1->alloc_keys = 0; 1019 command_key_array *arr2 = &cfg->command_keys[cfg->num_langs]; 1020 arr2->keys = NULL; 1021 arr2->num_keys = arr2->alloc_keys = 0; 1022 command_array *arr3 = &cfg->cmds[cfg->num_langs]; 1023 arr3->cmds = NULL; 1024 arr3->num_cmds = arr3->alloc_cmds = 0; 1025 cfg->langs[cfg->num_langs] = NULL; 1026 cfg->num_langs++; 1027 } 1028 1029 #define GEN_PARSE_STATEMENT(name, cb_type, mapping_type, mode_parse_func) \ 1030 static int \ 1031 name(ast_statement *st, mapping_type *m, char *filename, char **errstr) { \ 1032 size_t line = st->func_tok.line; \ 1033 size_t line_offset = st->func_tok.line_offset; \ 1034 m->cb = NULL; \ 1035 m->text = NULL; \ 1036 m->mods = 0; \ 1037 m->modes = 0; \ 1038 m->keysym = 0; \ 1039 char *msg = NULL; \ 1040 if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len < 1) { \ 1041 msg = "Invalid statement"; \ 1042 goto error; \ 1043 } \ 1044 m->cb = cb_type##_map_get_entry(st->args[0].text, st->args[0].len); \ 1045 if (!m->cb) { \ 1046 msg = "Invalid function specification"; \ 1047 goto error; \ 1048 } \ 1049 int text_init = 0, keysym_init = 0, modes_init = 0, mods_init = 0; \ 1050 for (size_t i = 1; i < st->len; i++) { \ 1051 line = st->args[i].line; \ 1052 line_offset = st->args[i].line_offset; \ 1053 if (str_array_equal("mods", st->args[i].text, st->args[i].len)) { \ 1054 if (mods_init) { \ 1055 msg = "Duplicate mods specification"; \ 1056 goto error; \ 1057 } else if (i == st->len - 1) { \ 1058 msg = "Unfinished statement"; \ 1059 goto error; \ 1060 } \ 1061 i++; \ 1062 if (parse_modmask(st->args[i].text, st->args[i].len, &m->mods)) { \ 1063 msg = "Invalid mods specification"; \ 1064 goto error; \ 1065 } \ 1066 mods_init = 1; \ 1067 } else if (str_array_equal("modes", st->args[i].text, st->args[i].len)) { \ 1068 if (modes_init) { \ 1069 msg = "Duplicate modes specification"; \ 1070 goto error; \ 1071 } else if (i == st->len - 1) { \ 1072 msg = "Unfinished statement"; \ 1073 goto error; \ 1074 } \ 1075 i++; \ 1076 if (mode_parse_func(st->args[i].text, st->args[i].len, &m->modes)) { \ 1077 msg = "Invalid modes specification"; \ 1078 goto error; \ 1079 } else if (!cb_type##_modemask_is_valid(m->cb, m->modes)) { \ 1080 msg = "Function not defined for all given modes"; \ 1081 goto error; \ 1082 } \ 1083 modes_init = 1; \ 1084 } else if (str_array_equal("keysym", st->args[i].text, st->args[i].len)) { \ 1085 if (text_init) { \ 1086 msg = "Text already specified"; \ 1087 goto error; \ 1088 } else if (keysym_init) { \ 1089 msg = "Duplicate keysym specification"; \ 1090 goto error; \ 1091 } else if (i == st->len - 1) { \ 1092 msg = "Unfinished statement"; \ 1093 goto error; \ 1094 } \ 1095 i++; \ 1096 if (parse_keysym(st->args[i].text, st->args[i].len, &m->keysym)) { \ 1097 msg = "Invalid keysym specification"; \ 1098 goto error; \ 1099 } \ 1100 keysym_init = 1; \ 1101 } else if (str_array_equal("text", st->args[i].text, st->args[i].len)) { \ 1102 if (keysym_init) { \ 1103 msg = "Keysym already specified"; \ 1104 goto error; \ 1105 } else if (text_init) { \ 1106 msg = "Duplicate text specification"; \ 1107 goto error; \ 1108 } else if (i == st->len - 1) { \ 1109 msg = "Unfinished statement"; \ 1110 goto error; \ 1111 } \ 1112 i++; \ 1113 m->text = ledit_strndup(st->args[i].text, st->args[i].len); \ 1114 text_init = 1; \ 1115 } else if (str_array_equal("catchall", st->args[i].text, st->args[i].len)) { \ 1116 if (keysym_init) { \ 1117 msg = "Keysym already specified"; \ 1118 goto error; \ 1119 } else if (text_init) { \ 1120 msg = "Duplicate text specification"; \ 1121 goto error; \ 1122 } \ 1123 m->text = ledit_strdup(""); \ 1124 text_init = 1; \ 1125 } else { \ 1126 msg = "Invalid statement"; \ 1127 goto error; \ 1128 } \ 1129 } \ 1130 if (!text_init && !keysym_init) { \ 1131 msg = "No text or keysym specified"; \ 1132 goto error; \ 1133 } \ 1134 if (!modes_init) { \ 1135 msg = "No modes specified"; \ 1136 goto error; \ 1137 } \ 1138 return 0; \ 1139 error: \ 1140 if (msg) { \ 1141 *errstr = print_fmt( \ 1142 "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg \ 1143 ); \ 1144 } \ 1145 if (m->text) \ 1146 free(m->text); \ 1147 return 1; \ 1148 } 1149 1150 GEN_PARSE_STATEMENT(parse_basic_key_statement, basic_key_cb, basic_key_mapping, parse_modemask) 1151 GEN_PARSE_STATEMENT(parse_command_key_statement, command_key_cb, command_key_mapping, parse_command_modemask) 1152 1153 static int 1154 parse_command_statement(ast_statement *st, command_mapping *m, char *filename, char **errstr) { 1155 size_t line = st->func_tok.line; 1156 size_t line_offset = st->func_tok.line_offset; 1157 m->cb = NULL; 1158 m->text = NULL; 1159 char *msg = NULL; 1160 if (!str_array_equal("bind", st->func_tok.text, st->func_tok.len) || st->len != 2) { 1161 msg = "Invalid statement"; 1162 goto error; 1163 } 1164 m->cb = command_cb_map_get_entry(st->args[0].text, st->args[0].len); 1165 if (!m->cb) { 1166 msg = "Invalid function specification"; 1167 goto error; 1168 } 1169 m->text = ledit_strndup(st->args[1].text, st->args[1].len); 1170 return 0; 1171 error: 1172 if (msg) { 1173 *errstr = print_fmt( 1174 "%s, line %zu, offset %zu: %s", filename, line, line_offset, msg 1175 ); 1176 } 1177 /* I guess this is unnecessary */ 1178 if (m->text) 1179 free(m->text); 1180 return 1; 1181 } 1182 1183 static void 1184 push_basic_key_mapping(basic_key_array *arr, basic_key_mapping m) { 1185 if (arr->num_keys == arr->alloc_keys) { 1186 arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1)); 1187 arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(basic_key_mapping)); 1188 } 1189 arr->keys[arr->num_keys] = m; 1190 arr->num_keys++; 1191 } 1192 1193 static void 1194 push_command_key_mapping(command_key_array *arr, command_key_mapping m) { 1195 if (arr->num_keys == arr->alloc_keys) { 1196 arr->alloc_keys = ideal_array_size(arr->alloc_keys, add_sz(arr->num_keys, 1)); 1197 arr->keys = ledit_reallocarray(arr->keys, arr->alloc_keys, sizeof(command_key_mapping)); 1198 } 1199 arr->keys[arr->num_keys] = m; 1200 arr->num_keys++; 1201 } 1202 1203 static void 1204 push_command_mapping(command_array *arr, command_mapping m) { 1205 if (arr->num_cmds == arr->alloc_cmds) { 1206 arr->alloc_cmds = ideal_array_size(arr->alloc_cmds, add_sz(arr->num_cmds, 1)); 1207 arr->cmds = ledit_reallocarray(arr->cmds, arr->alloc_cmds, sizeof(command_mapping)); 1208 } 1209 arr->cmds[arr->num_cmds] = m; 1210 arr->num_cmds++; 1211 } 1212 1213 /* FIXME: This could be made a lot nicer and less repetitive */ 1214 static int 1215 load_bindings(struct config *cfg, ast_list *list, char *filename, char **errstr) { 1216 int basic_keys_init = 0, command_keys_init = 0, commands_init = 0; 1217 size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */ 1218 for (size_t i = 0; i < list->len; i++) { 1219 size_t line = list->objs[i].tok.line; 1220 size_t line_offset = list->objs[i].tok.line_offset; 1221 if (list->objs[i].type != OBJ_ASSIGNMENT) { 1222 *errstr = print_fmt( 1223 "%s: Invalid statement in bindings configuration " 1224 "at list %zu, offset %zu", filename, line, line_offset 1225 ); 1226 goto error; 1227 } 1228 char *key = list->objs[i].obj.assignment.tok.text; 1229 size_t key_len = list->objs[i].obj.assignment.tok.len; 1230 if (str_array_equal("language", key, key_len)) { 1231 if (list->objs[i].obj.assignment.value->type != OBJ_STRING) { 1232 *errstr = print_fmt( 1233 "%s: Invalid language setting in bindings configuration " 1234 "at line %zu, offset %zu", filename, line, line_offset 1235 ); 1236 goto error; 1237 } else if (cfg->langs[cur_lang]) { 1238 *errstr = print_fmt( 1239 "%s: Duplicate language setting in bindings configuration " 1240 "at line %zu, offset %zu", filename, line, line_offset 1241 ); 1242 goto error; 1243 } 1244 char *val = list->objs[i].obj.assignment.value->obj.str.tok.text; 1245 size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len; 1246 cfg->langs[cur_lang] = ledit_strndup(val, val_len); 1247 } else if (str_array_equal("basic-keys", key, key_len)) { 1248 if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { 1249 *errstr = print_fmt( 1250 "%s: Invalid basic-keys setting in bindings configuration " 1251 "at line %zu, offset %zu", filename, line, line_offset 1252 ); 1253 goto error; 1254 } else if (basic_keys_init) { 1255 *errstr = print_fmt( 1256 "%s: Duplicate basic-keys setting in bindings configuration " 1257 "at line %zu, offset %zu", filename, line, line_offset 1258 ); 1259 goto error; 1260 } 1261 ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; 1262 for (size_t j = 0; j < slist->len; j++) { 1263 line = slist->objs[j].tok.line; 1264 line_offset = slist->objs[j].tok.line_offset; 1265 if (slist->objs[j].type != OBJ_STATEMENT) { 1266 *errstr = print_fmt( 1267 "%s: Invalid basic-keys setting in bindings configuration " 1268 "at line %zu, offset %zu", filename, line, line_offset 1269 ); 1270 goto error; 1271 } 1272 basic_key_mapping m; 1273 if (parse_basic_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr)) 1274 goto error; 1275 push_basic_key_mapping(&cfg->basic_keys[0], m); 1276 } 1277 basic_keys_init = 1; 1278 } else if (str_array_equal("command-keys", key, key_len)) { 1279 if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { 1280 *errstr = print_fmt( 1281 "%s: Invalid command-keys setting in bindings configuration " 1282 "at line %zu, offset %zu", filename, line, line_offset 1283 ); 1284 goto error; 1285 } else if (command_keys_init) { 1286 *errstr = print_fmt( 1287 "%s: Duplicate command-keys setting in bindings configuration " 1288 "at line %zu, offset %zu", filename, line, line_offset 1289 ); 1290 goto error; 1291 } 1292 ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; 1293 for (size_t j = 0; j < slist->len; j++) { 1294 line = slist->objs[j].tok.line; 1295 line_offset = slist->objs[j].tok.line_offset; 1296 if (slist->objs[j].type != OBJ_STATEMENT) { 1297 *errstr = print_fmt( 1298 "%s: Invalid command-keys setting in bindings configuration " 1299 "at line %zu, offset %zu", filename, line, line_offset 1300 ); 1301 goto error; 1302 } 1303 command_key_mapping m; 1304 if (parse_command_key_statement(&slist->objs[j].obj.statement, &m, filename, errstr)) 1305 goto error; 1306 push_command_key_mapping(&cfg->command_keys[0], m); 1307 } 1308 command_keys_init = 1; 1309 } else if (str_array_equal("commands", key, key_len)) { 1310 if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { 1311 *errstr = print_fmt( 1312 "%s: Invalid commands setting in bindings configuration " 1313 "at line %zu, offset %zu", filename, line, line_offset 1314 ); 1315 goto error; 1316 } else if (commands_init) { 1317 *errstr = print_fmt( 1318 "%s: Duplicate commands setting in bindings configuration " 1319 "at line %zu, offset %zu", filename, line, line_offset 1320 ); 1321 goto error; 1322 } 1323 ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; 1324 for (size_t j = 0; j < slist->len; j++) { 1325 line = slist->objs[j].tok.line; 1326 line_offset = slist->objs[j].tok.line_offset; 1327 if (slist->objs[j].type != OBJ_STATEMENT) { 1328 *errstr = print_fmt( 1329 "%s: Invalid commands setting in bindings configuration " 1330 "at line %zu, offset %zu", filename, line, line_offset 1331 ); 1332 goto error; 1333 } 1334 command_mapping m; 1335 if (parse_command_statement(&slist->objs[j].obj.statement, &m, filename, errstr)) 1336 goto error; 1337 push_command_mapping(&cfg->cmds[0], m); 1338 } 1339 commands_init = 1; 1340 } 1341 } 1342 1343 /* FIXME: the behavior here is a bit weird - if there is nothing other than a language 1344 setting in the bindings configuration, all actual bindings are default, but the 1345 associated language is different */ 1346 if (!cfg->langs[cur_lang]) { 1347 cfg->langs[cur_lang] = ledit_strdup(language_default); 1348 } 1349 /* FIXME: avoid calling strlen */ 1350 if (!basic_keys_init) { 1351 ledit_debug("No basic keys configured in bindings; loading defaults\n"); 1352 basic_key_mapping m; 1353 for (size_t i = 0; i < LENGTH(basic_keys_default); i++) { 1354 m.cb = basic_key_cb_map_get_entry(basic_keys_default[i].func_name, strlen(basic_keys_default[i].func_name)); 1355 if (!m.cb) { 1356 *errstr = print_fmt("default config: Invalid basic key function name '%s'", basic_keys_default[i].func_name); 1357 goto error; 1358 } else if (!basic_key_cb_modemask_is_valid(m.cb, basic_keys_default[i].modes)) { 1359 *errstr = print_fmt("default config: Function '%s' not defined for all given modes", basic_keys_default[i].func_name); 1360 goto error; 1361 } 1362 m.text = basic_keys_default[i].text ? ledit_strdup(basic_keys_default[i].text) : NULL; 1363 m.mods = basic_keys_default[i].mods; 1364 m.modes = basic_keys_default[i].modes; 1365 m.keysym = basic_keys_default[i].keysym; 1366 push_basic_key_mapping(&cfg->basic_keys[0], m); 1367 } 1368 } 1369 if (!command_keys_init) { 1370 ledit_debug("No command keys configured in bindings; loading defaults\n"); 1371 command_key_mapping m; 1372 for (size_t i = 0; i < LENGTH(command_keys_default); i++) { 1373 m.cb = command_key_cb_map_get_entry(command_keys_default[i].func_name, strlen(command_keys_default[i].func_name)); 1374 if (!m.cb) { 1375 *errstr = print_fmt("default config: Invalid command key function name '%s'", command_keys_default[i].func_name); 1376 goto error; 1377 } else if (!command_key_cb_modemask_is_valid(m.cb, command_keys_default[i].modes)) { 1378 *errstr = print_fmt("default config: Function '%s' not defined for all given modes", command_keys_default[i].func_name); 1379 goto error; 1380 } 1381 m.text = command_keys_default[i].text ? ledit_strdup(command_keys_default[i].text) : NULL; 1382 m.mods = command_keys_default[i].mods; 1383 m.modes = command_keys_default[i].modes; 1384 m.keysym = command_keys_default[i].keysym; 1385 push_command_key_mapping(&cfg->command_keys[0], m); 1386 } 1387 } 1388 /* FIXME: guard against NULL text in default config! */ 1389 if (!commands_init) { 1390 ledit_debug("No commands configured in bindings; loading defaults\n"); 1391 command_mapping m; 1392 for (size_t i = 0; i < LENGTH(commands_default); i++) { 1393 m.cb = command_cb_map_get_entry(commands_default[i].func_name, strlen(commands_default[i].func_name)); 1394 if (!m.cb) { 1395 *errstr = print_fmt("default config: Invalid command function name '%s'", commands_default[i].func_name); 1396 goto error; 1397 } 1398 m.text = ledit_strdup(commands_default[i].text); 1399 push_command_mapping(&cfg->cmds[0], m); 1400 } 1401 } 1402 return 0; 1403 /* FIXME: simplify error handling by doing more here */ 1404 error: 1405 return 1; 1406 } 1407 1408 static int 1409 load_mapping(struct config *cfg, ast_list *list, char *filename, char **errstr) { 1410 int key_mapping_init = 0, command_mapping_init = 0; 1411 size_t cur_lang = cfg->num_langs - 1; /* FIXME: ensure no underflow */ 1412 for (size_t i = 0; i < list->len; i++) { 1413 size_t line = list->objs[i].tok.line; 1414 size_t line_offset = list->objs[i].tok.line_offset; 1415 if (list->objs[i].type != OBJ_ASSIGNMENT) { 1416 *errstr = print_fmt( 1417 "%s: Invalid statement in language mapping configuration " 1418 "at list %zu, offset %zu", filename, line, line_offset 1419 ); 1420 goto error; 1421 } 1422 char *key = list->objs[i].obj.assignment.tok.text; 1423 size_t key_len = list->objs[i].obj.assignment.tok.len; 1424 basic_key_array *bkmap = &cfg->basic_keys[cur_lang]; 1425 command_key_array *ckmap = &cfg->command_keys[cur_lang]; 1426 command_array *cmap = &cfg->cmds[cur_lang]; 1427 if (str_array_equal("language", key, key_len)) { 1428 if (list->objs[i].obj.assignment.value->type != OBJ_STRING) { 1429 *errstr = print_fmt( 1430 "%s: Invalid language setting in language mapping configuration " 1431 "at line %zu, offset %zu", filename, line, line_offset 1432 ); 1433 goto error; 1434 } else if (cfg->langs[cur_lang]) { 1435 *errstr = print_fmt( 1436 "%s: Duplicate language setting in language mapping configuration " 1437 "at line %zu, offset %zu", filename, line, line_offset 1438 ); 1439 goto error; 1440 } 1441 char *val = list->objs[i].obj.assignment.value->obj.str.tok.text; 1442 size_t val_len = list->objs[i].obj.assignment.value->obj.str.tok.len; 1443 cfg->langs[cur_lang] = ledit_strndup(val, val_len); 1444 } else if (str_array_equal("key-mapping", key, key_len)) { 1445 if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { 1446 *errstr = print_fmt( 1447 "%s: Invalid key-mapping setting in language mapping configuration " 1448 "at line %zu, offset %zu", filename, line, line_offset 1449 ); 1450 goto error; 1451 } else if (key_mapping_init) { 1452 *errstr = print_fmt( 1453 "%s: Duplicate key-mapping setting in language mapping configuration " 1454 "at line %zu, offset %zu", filename, line, line_offset 1455 ); 1456 goto error; 1457 } 1458 ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; 1459 for (size_t j = 0; j < slist->len; j++) { 1460 line = slist->objs[j].tok.line; 1461 line_offset = slist->objs[j].tok.line_offset; 1462 if (slist->objs[j].type != OBJ_STATEMENT) { 1463 *errstr = print_fmt( 1464 "%s: Invalid key-mapping setting in language mapping configuration " 1465 "at line %zu, offset %zu", filename, line, line_offset 1466 ); 1467 goto error; 1468 } 1469 ast_statement *st = &slist->objs[j].obj.statement; 1470 if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) { 1471 *errstr = print_fmt( 1472 "%s: Invalid key-mapping statement in language mapping configuration " 1473 "at line %zu, offset %zu", filename, line, line_offset 1474 ); 1475 goto error; 1476 } 1477 /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */ 1478 for (size_t k = 0; k < bkmap->num_keys; k++) { 1479 if (bkmap->keys[k].text && str_array_equal(bkmap->keys[k].text, st->args[1].text, st->args[1].len)) { 1480 free(bkmap->keys[k].text); 1481 bkmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len); 1482 } 1483 } 1484 for (size_t k = 0; k < ckmap->num_keys; k++) { 1485 if (ckmap->keys[k].text && str_array_equal(ckmap->keys[k].text, st->args[1].text, st->args[1].len)) { 1486 free(ckmap->keys[k].text); 1487 ckmap->keys[k].text = ledit_strndup(st->args[0].text, st->args[0].len); 1488 } 1489 } 1490 } 1491 key_mapping_init = 1; 1492 } else if (str_array_equal("command-mapping", key, key_len)) { 1493 if (list->objs[i].obj.assignment.value->type != OBJ_LIST) { 1494 *errstr = print_fmt( 1495 "%s: Invalid command-mapping setting in language mapping configuration " 1496 "at line %zu, offset %zu", filename, line, line_offset 1497 ); 1498 goto error; 1499 } else if (command_mapping_init) { 1500 *errstr = print_fmt( 1501 "%s: Duplicate command-mapping setting in language mapping configuration " 1502 "at line %zu, offset %zu", filename, line, line_offset 1503 ); 1504 goto error; 1505 } 1506 ast_list *slist = &list->objs[i].obj.assignment.value->obj.list; 1507 for (size_t j = 0; j < slist->len; j++) { 1508 line = slist->objs[j].tok.line; 1509 line_offset = slist->objs[j].tok.line_offset; 1510 if (slist->objs[j].type != OBJ_STATEMENT) { 1511 *errstr = print_fmt( 1512 "%s: Invalid command-mapping setting in language mapping configuration " 1513 "at line %zu, offset %zu", filename, line, line_offset 1514 ); 1515 goto error; 1516 } 1517 ast_statement *st = &slist->objs[j].obj.statement; 1518 if (!str_array_equal("map", st->func_tok.text, st->func_tok.len) || st->len != 2) { 1519 *errstr = print_fmt( 1520 "%s: Invalid command-mapping statement in language mapping configuration " 1521 "at line %zu, offset %zu", filename, line, line_offset 1522 ); 1523 goto error; 1524 } 1525 for (size_t k = 0; k < cmap->num_cmds; k++) { 1526 if (str_array_equal(cmap->cmds[k].text, st->args[1].text, st->args[1].len)) { 1527 free(cmap->cmds[k].text); 1528 cmap->cmds[k].text = ledit_strndup(st->args[0].text, st->args[0].len); 1529 } 1530 } 1531 } 1532 command_mapping_init = 1; 1533 } 1534 } 1535 if (!cfg->langs[cur_lang]) { 1536 /* FIXME: pass actual beginning line and offset so this doesn't have to 1537 use the line and offset of the first list element */ 1538 if (list->len > 0) { 1539 *errstr = print_fmt( 1540 "%s: Missing language setting in language mapping configuration " 1541 "at line %zu, offset %zu", filename, list->objs[0].tok.line, list->objs[0].tok.line_offset 1542 ); 1543 } else { 1544 *errstr = print_fmt("%s: Missing language setting in language mapping configuration", filename); 1545 } 1546 goto error; 1547 } 1548 return 0; 1549 error: 1550 return 1; 1551 } 1552 1553 static void 1554 append_mapping(struct config *cfg) { 1555 push_lang(cfg); 1556 ledit_assert(cfg->num_langs > 1); 1557 1558 /* first duplicate original mappings before replacing the text */ 1559 /* FIXME: optimize this to avoid useless reallocations */ 1560 size_t cur_lang = cfg->num_langs - 1; 1561 basic_key_array *arr1 = &cfg->basic_keys[cur_lang]; 1562 arr1->num_keys = arr1->alloc_keys = cfg->basic_keys[0].num_keys; 1563 arr1->keys = ledit_reallocarray(NULL, arr1->num_keys, sizeof(basic_key_mapping)); 1564 memmove(arr1->keys, cfg->basic_keys[0].keys, arr1->num_keys * sizeof(basic_key_mapping)); 1565 for (size_t i = 0; i < arr1->num_keys; i++) { 1566 if (arr1->keys[i].text) 1567 arr1->keys[i].text = ledit_strdup(arr1->keys[i].text); 1568 } 1569 1570 1571 command_key_array *arr2 = &cfg->command_keys[cur_lang]; 1572 arr2->num_keys = arr2->alloc_keys = cfg->command_keys[0].num_keys; 1573 arr2->keys = ledit_reallocarray(NULL, arr2->num_keys, sizeof(command_key_mapping)); 1574 memmove(arr2->keys, cfg->command_keys[0].keys, arr2->num_keys * sizeof(command_key_mapping)); 1575 for (size_t i = 0; i < arr2->num_keys; i++) { 1576 if (arr2->keys[i].text) 1577 arr2->keys[i].text = ledit_strdup(arr2->keys[i].text); 1578 } 1579 1580 command_array *arr3 = &cfg->cmds[cur_lang]; 1581 arr3->num_cmds = arr3->alloc_cmds = cfg->cmds[0].num_cmds; 1582 arr3->cmds = ledit_reallocarray(NULL, arr3->num_cmds, sizeof(command_mapping)); 1583 memmove(arr3->cmds, cfg->cmds[0].cmds, arr3->num_cmds * sizeof(command_mapping)); 1584 for (size_t i = 0; i < arr3->num_cmds; i++) { 1585 arr3->cmds[i].text = ledit_strdup(arr3->cmds[i].text); 1586 } 1587 } 1588 1589 #ifdef LEDIT_DEBUG 1590 static void 1591 debug_print_obj(ast_obj *obj, int shiftwidth) { 1592 for (int i = 0; i < shiftwidth; i++) { 1593 fprintf(stderr, " "); 1594 } 1595 switch (obj->type) { 1596 case OBJ_STRING: 1597 fprintf(stderr, "STRING: %.*s\n", (int)obj->obj.str.tok.len, obj->obj.str.tok.text); 1598 break; 1599 case OBJ_STATEMENT: 1600 fprintf(stderr, "STATEMENT: %.*s ", (int)obj->obj.statement.func_tok.len, obj->obj.statement.func_tok.text); 1601 for (size_t i = 0; i < obj->obj.statement.len; i++) { 1602 fprintf(stderr, "%.*s ", (int)obj->obj.statement.args[i].len, obj->obj.statement.args[i].text); 1603 } 1604 fprintf(stderr, "\n"); 1605 break; 1606 case OBJ_ASSIGNMENT: 1607 fprintf(stderr, "ASSIGNMENT: %.*s =\n", (int)obj->obj.assignment.tok.len, obj->obj.assignment.tok.text); 1608 debug_print_obj(obj->obj.assignment.value, shiftwidth + 4); 1609 break; 1610 case OBJ_LIST: 1611 fprintf(stderr, "LIST:\n"); 1612 for (size_t i = 0; i < obj->obj.list.len; i++) { 1613 debug_print_obj(&obj->obj.list.objs[i], shiftwidth + 4); 1614 } 1615 break; 1616 } 1617 } 1618 #endif 1619 1620 /* WARNING: *errstr must be freed! */ 1621 int 1622 config_loadfile(ledit_common *common, char *filename, char **errstr) { 1623 #ifdef LEDIT_DEBUG 1624 struct timespec now, elapsed, last; 1625 clock_gettime(CLOCK_MONOTONIC, &last); 1626 #endif 1627 size_t len; 1628 *errstr = NULL; 1629 ast_list list = {.objs = NULL, .len = 0, .cap = 0}; 1630 char *file_contents = NULL; 1631 if (filename) { 1632 file_contents = load_file(filename, &len, errstr); 1633 if (!file_contents) return 1; 1634 #ifdef LEDIT_DEBUG 1635 clock_gettime(CLOCK_MONOTONIC, &now); 1636 ledit_timespecsub(&now, &last, &elapsed); 1637 ledit_debug_fmt( 1638 "Time to load config file: %lld seconds, %ld nanoseconds\n", 1639 (long long)elapsed.tv_sec, elapsed.tv_nsec 1640 ); 1641 last = now; 1642 #endif 1643 /* start at line 1 to make error messages more useful */ 1644 struct lexstate s = {file_contents, len, 0, 1, 0}; 1645 if (parse_list(&s, &list, 1, filename, errstr)) { 1646 free(file_contents); 1647 return 1; 1648 } 1649 #ifdef LEDIT_DEBUG 1650 clock_gettime(CLOCK_MONOTONIC, &now); 1651 ledit_timespecsub(&now, &last, &elapsed); 1652 ledit_debug_fmt( 1653 "Time to parse config file: %lld seconds, %ld nanoseconds\n", 1654 (long long)elapsed.tv_sec, elapsed.tv_nsec 1655 ); 1656 #endif 1657 } 1658 1659 #ifdef LEDIT_DEBUG 1660 clock_gettime(CLOCK_MONOTONIC, &last); 1661 for (size_t i = 0; i < list.len; i++) { 1662 debug_print_obj(&list.objs[i], 0); 1663 } 1664 clock_gettime(CLOCK_MONOTONIC, &now); 1665 ledit_timespecsub(&now, &last, &elapsed); 1666 ledit_debug_fmt( 1667 "Time to print useless information: %lld seconds, %ld nanoseconds\n", 1668 (long long)elapsed.tv_sec, elapsed.tv_nsec 1669 ); 1670 clock_gettime(CLOCK_MONOTONIC, &last); 1671 #endif 1672 1673 struct config cfg = {NULL, NULL, NULL, NULL, NULL, 0, 0}; 1674 int theme_init = 0, bindings_init = 0, mappings_init = 0; 1675 ast_assignment *assignment; 1676 for (size_t i = 0; i < list.len; i++) { 1677 switch (list.objs[i].type) { 1678 case OBJ_ASSIGNMENT: 1679 assignment = &list.objs[i].obj.assignment; 1680 if (str_array_equal("theme", assignment->tok.text, assignment->tok.len)) { 1681 if (theme_init) { 1682 *errstr = print_fmt( 1683 "%s: Duplicate theme definition at line %zu, offset %zu", 1684 filename, assignment->tok.line, assignment->tok.line_offset 1685 ); 1686 goto error; 1687 } else if (assignment->value->type != OBJ_LIST) { 1688 *errstr = print_fmt( 1689 "%s: Invalid theme definition at line %zu, offset %zu", 1690 filename, assignment->tok.line, assignment->tok.line_offset 1691 ); 1692 goto error; 1693 } 1694 cfg.theme = load_theme(common, &assignment->value->obj.list, filename, errstr); 1695 if (!cfg.theme) 1696 goto error; 1697 theme_init = 1; 1698 } else if (str_array_equal("bindings", assignment->tok.text, assignment->tok.len)) { 1699 if (bindings_init) { 1700 *errstr = print_fmt( 1701 "%s: Duplicate definition of bindings at line %zu, offset %zu", 1702 filename, assignment->tok.line, assignment->tok.line_offset 1703 ); 1704 goto error; 1705 } 1706 push_lang(&cfg); 1707 if (assignment->value->type != OBJ_LIST) { 1708 *errstr = print_fmt( 1709 "%s: Invalid definition of bindings at line %zu, offset %zu", 1710 filename, assignment->tok.line, assignment->tok.line_offset 1711 ); 1712 goto error; 1713 } 1714 if (load_bindings(&cfg, &assignment->value->obj.list, filename, errstr)) 1715 goto error; 1716 bindings_init = 1; 1717 } else if (str_array_equal("language-mapping", assignment->tok.text, assignment->tok.len)) { 1718 if (cfg.num_langs == 0) { 1719 ledit_debug("No key/command bindings configured; loading defaults\n"); 1720 push_lang(&cfg); 1721 /* load default config */ 1722 ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0}; 1723 /* shouldn't usually happen */ 1724 if (load_bindings(&cfg, &empty_list, filename, errstr)) 1725 goto error; 1726 bindings_init = 1; 1727 } else if (assignment->value->type != OBJ_LIST) { 1728 *errstr = print_fmt( 1729 "%s: Invalid definition of language mapping at line %zu, offset %zu", 1730 filename, assignment->tok.line, assignment->tok.line_offset 1731 ); 1732 goto error; 1733 } 1734 1735 append_mapping(&cfg); 1736 1737 if (load_mapping(&cfg, &assignment->value->obj.list, filename, errstr)) 1738 goto error; 1739 mappings_init = 1; 1740 } else { 1741 *errstr = print_fmt( 1742 "%s: Invalid assignment at line %zu, offset %zu", 1743 filename, assignment->tok.line, assignment->tok.line_offset 1744 ); 1745 goto error; 1746 } 1747 break; 1748 default: 1749 *errstr = print_fmt( 1750 "%s: Invalid statement at line %zu, offset %zu", 1751 filename, list.objs[i].tok.line, list.objs[i].tok.line_offset 1752 ); 1753 goto error; 1754 } 1755 } 1756 if (!theme_init) { 1757 ledit_debug("No theme configured; loading defaults\n"); 1758 cfg.theme = load_theme(common, NULL, NULL, errstr); 1759 if (!cfg.theme) 1760 goto error; 1761 } 1762 if (!bindings_init) { 1763 ledit_debug("No key/command bindings configured; loading defaults\n"); 1764 push_lang(&cfg); 1765 /* load default config */ 1766 ast_list empty_list = {.objs = NULL, .len = 0, .cap = 0}; 1767 /* shouldn't usually happen */ 1768 if (load_bindings(&cfg, &empty_list, NULL, errstr)) 1769 goto error; 1770 } 1771 if (!mappings_init) { 1772 ledit_debug("No key/command mappings configured; loading defaults\n"); 1773 for (size_t i = 0; i < LENGTH(mappings_default); i++) { 1774 append_mapping(&cfg); 1775 size_t cur_lang = cfg.num_langs - 1; 1776 cfg.langs[cur_lang] = ledit_strdup(mappings_default[i].lang); 1777 basic_key_array *bkmap = &cfg.basic_keys[cur_lang]; 1778 command_key_array *ckmap = &cfg.command_keys[cur_lang]; 1779 command_array *cmap = &cfg.cmds[cur_lang]; 1780 /* FIXME: any way to speed this up? I guess once the keys can be binary-searched... */ 1781 /* FIXME: duplicated code from above */ 1782 for (size_t j = 0; j < mappings_default[i].keys_len; j++) { 1783 for (size_t k = 0; k < bkmap->num_keys; k++) { 1784 if (bkmap->keys[k].text && !strcmp(bkmap->keys[k].text, mappings_default[i].keys[j].to)) { 1785 free(bkmap->keys[k].text); 1786 bkmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from); 1787 } 1788 } 1789 for (size_t k = 0; k < ckmap->num_keys; k++) { 1790 if (ckmap->keys[k].text && !strcmp(ckmap->keys[k].text, mappings_default[i].keys[j].to)) { 1791 free(ckmap->keys[k].text); 1792 ckmap->keys[k].text = ledit_strdup(mappings_default[i].keys[j].from); 1793 } 1794 } 1795 } 1796 for (size_t j = 0; j < mappings_default[i].cmds_len; j++) { 1797 for (size_t k = 0; k < cmap->num_cmds; k++) { 1798 if (!strcmp(cmap->cmds[k].text, mappings_default[i].keys[j].to)) { 1799 free(cmap->cmds[k].text); 1800 cmap->cmds[k].text = ledit_strdup(mappings_default[i].keys[j].from); 1801 } 1802 } 1803 } 1804 } 1805 } 1806 destroy_list(&list); 1807 free(file_contents); 1808 config_destroy(common, &config); 1809 config = cfg; 1810 #ifdef LEDIT_DEBUG 1811 clock_gettime(CLOCK_MONOTONIC, &now); 1812 ledit_timespecsub(&now, &last, &elapsed); 1813 ledit_debug_fmt( 1814 "Time to interpret config file: %lld seconds, %ld nanoseconds\n", 1815 (long long)elapsed.tv_sec, elapsed.tv_nsec 1816 ); 1817 #endif 1818 return 0; 1819 error: 1820 destroy_list(&list); 1821 free(file_contents); 1822 config_destroy(common, &cfg); 1823 return 1; 1824 } 1825 1826 ledit_theme * 1827 config_get_theme(void) { 1828 ledit_assert(config.theme != NULL); 1829 return config.theme; 1830 } 1831 1832 basic_key_array * 1833 config_get_basic_keys(size_t lang_index) { 1834 ledit_assert(lang_index < config.num_langs); 1835 return &config.basic_keys[lang_index]; 1836 } 1837 1838 command_key_array * 1839 config_get_command_keys(size_t lang_index) { 1840 ledit_assert(lang_index < config.num_langs); 1841 return &config.command_keys[lang_index]; 1842 } 1843 1844 command_array * 1845 config_get_commands(size_t lang_index) { 1846 ledit_assert(lang_index < config.num_langs); 1847 return &config.cmds[lang_index]; 1848 } 1849 1850 int 1851 config_get_language_index(char *lang, size_t *idx_ret) { 1852 for (size_t i = 0; i < config.num_langs; i++) { 1853 if (!strcmp(lang, config.langs[i])) { 1854 *idx_ret = i; 1855 return 0; 1856 } 1857 } 1858 return 1; 1859 } 1860 1861 char * 1862 config_get_language_string(size_t lang_index) { 1863 if (lang_index >= config.num_langs) 1864 return NULL; 1865 return config.langs[lang_index]; 1866 }