diff options
-rw-r--r-- | buffer.c | 4 | ||||
-rw-r--r-- | config.c | 913 | ||||
-rw-r--r-- | main.c | 6 | ||||
-rw-r--r-- | ted-internal.h | 35 |
4 files changed, 331 insertions, 627 deletions
@@ -85,9 +85,7 @@ struct TextBuffer { /// capacity of \ref lines u32 lines_capacity; - /// cached settings index (into ted->all_settings), or -1 if has not been computed yet - i32 settings_idx; - + Settings settings; /// which LSP this document is open in LSPID lsp_opened_in; /// determining which LSP to use for a buffer takes some work, @@ -18,17 +18,6 @@ typedef enum { SECTION_EXTENSIONS } ConfigSection; -struct ConfigPart { - /// index in order of which part was read first. - int index; - SettingsContext context; - ConfigSection section; - char *file; - u32 line; - /// contents of this config part - char *text; -}; - // all the "control" pointers here are relative to `settings_zero`. typedef struct { const char *name; @@ -168,39 +157,52 @@ static const SettingKeyCombo settings_key_combo[] = { }; -static void setting_bool_set(Settings *settings, const SettingBool *set, bool value) { - *(bool *)((char *)settings + ((char*)set->control - (char*)&settings_zero)) = value; +static void config_set_setting(Config *cfg, ptrdiff_t offset, const void *value, size_t size) { + memmove((char *)&cfg->settings + offset, value, size); + memset(&cfg->settings_set[offset], 1, size); +} + +static void config_set_bool(Config *cfg, const SettingBool *set, bool value) { + config_set_setting(cfg, (char*)set->control - (char*)&settings_zero, &value, sizeof value); } -static void setting_u8_set(Settings *settings, const SettingU8 *set, u8 value) { +static void config_set_u8(Config *cfg, const SettingU8 *set, u8 value) { if (value >= set->min && value <= set->max) - *(u8 *)((char *)settings + ((char*)set->control - (char*)&settings_zero)) = value; + config_set_setting(cfg, (char*)set->control - (char*)&settings_zero, &value, sizeof value); } -static void setting_u16_set(Settings *settings, const SettingU16 *set, u16 value) { +static void config_set_u16(Config *cfg, const SettingU16 *set, u16 value) { if (value >= set->min && value <= set->max) - *(u16 *)((char *)settings + ((char*)set->control - (char*)&settings_zero)) = value; + config_set_setting(cfg, (char*)set->control - (char*)&settings_zero, &value, sizeof value); } -static void setting_u32_set(Settings *settings, const SettingU32 *set, u32 value) { +static void config_set_u32(Config *cfg, const SettingU32 *set, u32 value) { if (value >= set->min && value <= set->max) - *(u32 *)((char *)settings + ((char*)set->control - (char*)&settings_zero)) = value; + config_set_setting(cfg, (char*)set->control - (char*)&settings_zero, &value, sizeof value); } -static void setting_float_set(Settings *settings, const SettingFloat *set, float value) { +static void config_set_float(Config *cfg, const SettingFloat *set, float value) { if (value >= set->min && value <= set->max) - *(float *)((char *)settings + ((char*)set->control - (char*)&settings_zero)) = value; + config_set_setting(cfg, (char*)set->control - (char*)&settings_zero, &value, sizeof value); } -static void setting_string_set(Settings *settings, const SettingString *set, const char *value) { - RcStr **control = (RcStr **)((char *)settings + ((char *)set->control - (char*)&settings_zero)); +static void config_set_key_combo(Config *cfg, const SettingKeyCombo *set, KeyCombo value) { + config_set_setting(cfg, (char *)set->control - (char *)&settings_zero, &value, sizeof value); +} +static void config_set_string(Config *cfg, const SettingString *set, const char *value) { + assert(value); + Settings *settings = &cfg->settings; + const ptrdiff_t offset = ((char *)set->control - (char*)&settings_zero); + RcStr **control = (RcStr **)((char *)settings + offset); if (*control) rc_str_decref(control); - *control = rc_str_new(value, -1); + RcStr *rc = rc_str_new(value, -1); + config_set_setting(cfg, offset, &rc, sizeof rc); } -static void setting_key_combo_set(Settings *settings, const SettingKeyCombo *set, KeyCombo value) { - KeyCombo *control = (KeyCombo *)((char *)settings + ((char*)set->control - (char*)&settings_zero)); - *control = value; +static void config_set_color(Config *cfg, ColorSetting setting, u32 color) { + config_set_setting(cfg, (char *)&settings_zero.colors[setting] - (char *)&settings_zero, &color, sizeof color); } + typedef struct { Ted *ted; const char *filename; + ConfigSection section; u32 line_number; // currently processing this line number bool error; } ConfigReader; @@ -218,57 +220,10 @@ static void config_err(ConfigReader *cfg, const char *fmt, ...) { ted_error(cfg->ted, "%s", error); } -static void context_copy(SettingsContext *dest, const SettingsContext *src) { - *dest = *src; - if (src->path) - dest->path = str_dup(src->path); -} - -long context_score(const char *path, Language lang, const SettingsContext *context) { - long score = 0; - - // currently contexts are ranked by: - // 1. path matching, the more specific the better - // 2. language - - if (context->language) { - if (lang == context->language) { - score += 1; - } else { - // dont use this. it's language-specific and for the wrong language. - return INT_MIN; - } - } - - if (context->path) { - if (path && str_has_path_prefix(path, context->path)) { - score += 2 * (long)strlen(context->path); - } else { - // dont use this. it's path-specific and for the wrong path. - return INT_MIN; - } - } - - return score; -} - -/* does being in the context of `parent` imply you are in the context of `child`? */ -static bool context_is_parent(const SettingsContext *parent, const SettingsContext *child) { - if (child->language == 0 && parent->language != 0) - return false; - if (parent->language != 0 && child->language != 0 && parent->language != child->language) - return false; - if (parent->path) { - if (!child->path) - return false; - if (!str_has_prefix(child->path, parent->path)) - return false; - } - return true; -} - +// if dest == src, this should still increment reference counts, etc. static void settings_copy(Settings *dest, const Settings *src) { - *dest = *src; + if (dest != src) + *dest = *src; gl_rc_sab_incref(dest->bg_shader); gl_rc_texture_incref(dest->bg_texture); @@ -277,21 +232,51 @@ static void settings_copy(Settings *dest, const Settings *src) { RcStr *rc = *(RcStr **)((char *)dest + ((char *)s->control - (char *)&settings_zero)); rc_str_incref(rc); } - context_copy(&dest->context, &src->context); dest->language_extensions = arr_copy(src->language_extensions); dest->key_actions = arr_copy(src->key_actions); } -static void context_free(SettingsContext *ctx) { - free(ctx->path); - memset(ctx, 0, sizeof *ctx); +static void settings_free(Settings *settings) { + arr_free(settings->language_extensions); + gl_rc_sab_decref(&settings->bg_shader); + gl_rc_texture_decref(&settings->bg_texture); + arr_free(settings->key_actions); + for (size_t i = 0; i < arr_count(settings_string); i++) { + const SettingString *s = &settings_string[i]; + RcStr **rc = (RcStr **)((char *)settings + ((char *)s->control - (char *)&settings_zero)); + rc_str_decref(rc); + } +} + +static void config_free(Config *cfg) { + settings_free(&cfg->settings); + free(cfg->path); + memset(cfg, 0, sizeof *cfg); } -static void config_part_free(ConfigPart *part) { - context_free(&part->context); - arr_clear(part->text); - free(part->file); - memset(part, 0, sizeof *part); +static void config_merge(Config *dest_cfg, const Config *src_cfg) { + Settings *dest = &dest_cfg->settings; + const Settings *src = &src_cfg->settings; + char *destc = (char *)dest; + const char *srcc = (const char *)src; + LanguageExtension *const dest_exts = dest->language_extensions; + LanguageExtension *const src_exts = src->language_extensions; + KeyAction *const dest_keys = dest->key_actions; + KeyAction *const src_keys = src->key_actions; + for (size_t i = 0; i < sizeof(Settings); i++) { + if (src_cfg->settings_set[i]) + destc[i] = srcc[i]; + } + // we don't want these to be replaced by src's + dest->language_extensions = dest_exts; + dest->key_actions = dest_keys; + // increment reference counts, etc. + settings_copy(dest, dest); + // merge language_extensions and key_actions + arr_foreach_ptr(src_exts, LanguageExtension, ext) + arr_add(dest->language_extensions, *ext); + arr_foreach_ptr(src_keys, KeyAction, act) + arr_add(dest->key_actions, *act); } static SDL_Keycode config_parse_key(ConfigReader *cfg, const char *str) { @@ -370,73 +355,6 @@ static KeyCombo config_parse_key_combo(ConfigReader *cfg, const char *str) { } -static void parse_section_header(ConfigReader *cfg, char *line, ConfigPart *part) { - #define SECTION_HEADER_HELP "Section headers should look like this: [(path//)(language.)section-name]" - Ted *ted = cfg->ted; - char *closing = strchr(line, ']'); - if (!closing) { - config_err(cfg, "Unmatched [. " SECTION_HEADER_HELP); - return; - } else if (closing[1] != '\0') { - config_err(cfg, "Text after section. " SECTION_HEADER_HELP); - return; - } else { - *closing = '\0'; - char *section = line + 1; - char *path_end = strstr(section, "//"); - if (path_end) { - size_t path_len = (size_t)(path_end - section); - char path[TED_PATH_MAX]; - path[0] = '\0'; - - // expand ~ - if (section[0] == '~') { - str_cpy(path, sizeof path, ted->home); - ++section; - --path_len; - } - strn_cat(path, sizeof path, section, path_len); - #if _WIN32 - // replace forward slashes with backslashes - for (char *p = path; *p; ++p) - if (*p == '/') - *p = '\\'; - #endif - part->context.path = str_dup(path); - section = path_end + 2; - } - - char *dot = strchr(section, '.'); - - if (dot) { - *dot = '\0'; - Language language = part->context.language = language_from_str(section); - if (!language) { - config_err(cfg, "Unrecognized language: %s.", section); - return; - } - section = dot + 1; - } - - if (streq(section, "keyboard")) { - part->section = SECTION_KEYBOARD; - } else if (streq(section, "colors")) { - part->section = SECTION_COLORS; - } else if (streq(section, "core")) { - part->section = SECTION_CORE; - } else if (streq(section, "extensions")) { - if (part->context.language != 0 || part->context.path) { - config_err(cfg, "Extensions section cannot be language- or path-specific."); - return; - } - part->section = SECTION_EXTENSIONS; - } else { - config_err(cfg, "Unrecognized section: [%s].", section); - return; - } - } -} - static bool settings_initialized = false; static SettingAny settings_all[1000] = {0}; @@ -513,141 +431,9 @@ static void get_config_path(Ted *ted, char *expanded, size_t expanded_sz, const } -static void config_read_(Ted *ted, ConfigPart **parts, const char *path, const char ***include_stack) { - // check for, e.g. %include ted.cfg inside ted.cfg - arr_foreach_ptr(*include_stack, const char *, p_include) { - if (streq(path, *p_include)) { - char text[1024]; - strbuf_cpy(text, "%include loop in config files: "); - strbuf_cat(text, (*include_stack)[0]); - for (u32 i = 1; i < arr_len(*include_stack); ++i) { - if (i > 1) - strbuf_cat(text, ", which"); - strbuf_catf(text, " includes %s", (*include_stack)[i]); - } - if (arr_len(*include_stack) > 1) - strbuf_cat(text, ", which"); - strbuf_catf(text, " includes %s", path); - ted_error(ted, "%s", text); - return; - } - } - arr_add(*include_stack, path); - - FILE *fp = fopen(path, "rb"); - if (!fp) { - ted_error(ted, "Couldn't open config file %s: %s.", path, strerror(errno)); - return; - } - - - ConfigReader cfg_reader = { - .ted = ted, - .filename = path, - .line_number = 1, - .error = false - }; - ConfigReader *cfg = &cfg_reader; - - ConfigPart *part = NULL; - - char line[4096] = {0}; - while (fgets(line, sizeof line, fp)) { - char *newline = strchr(line, '\n'); - if (!newline && !feof(fp)) { - config_err(cfg, "Line is too long."); - break; - } - - if (newline) *newline = '\0'; - char *carriage_return = strchr(line, '\r'); - if (carriage_return) *carriage_return = '\0'; - - if (line[0] == '[') { - // a new part! - part = arr_addp(*parts); - part->index = (int)arr_len(*parts); - part->file = str_dup(path); - part->line = cfg->line_number + 1; - parse_section_header(&cfg_reader, line, part); - } else if (line[0] == '%') { - if (str_has_prefix(line, "%include ")) { - char included[TED_PATH_MAX]; - char expanded[TED_PATH_MAX]; - strbuf_cpy(included, line + strlen("%include ")); - while (*included && isspace(included[strlen(included) - 1])) - included[strlen(included) - 1] = '\0'; - get_config_path(ted, expanded, sizeof expanded, included); - config_read_(ted, parts, expanded, include_stack); - } - } else if (part) { - for (int i = 0; line[i]; ++i) { - arr_add(part->text, line[i]); - } - arr_add(part->text, '\n'); - } else { - const char *p = line; - while (isspace(*p)) ++p; - if (*p == '\0' || *p == '#') { - // blank line - } else { - config_err(cfg, "Config has text before first section header."); - } - } - ++cfg->line_number; - } - - if (ferror(fp)) - ted_error(ted, "Error reading %s.", path); - fclose(fp); - arr_remove_last(*include_stack); -} - -void config_read(Ted *ted, ConfigPart **parts, const char *filename) { - const char **include_stack = NULL; - config_read_(ted, parts, filename, &include_stack); -} - -// IMPORTANT REQUIREMENT FOR THIS FUNCTION: -// - less specific contexts compare as less -// (i.e. if context_is_parent(a.context, b.context), then we return -1, and vice versa.) -// - this gives a total ordering; ties are broken by order of appearance -static int config_part_cmp(const ConfigPart *ap, const ConfigPart *bp) { - const SettingsContext *a = &ap->context, *b = &bp->context; - if (a->language == 0 && b->language != 0) - return -1; - if (a->language != 0 && b->language == 0) - return +1; - const char *a_path = a->path ? a->path : ""; - const char *b_path = b->path ? b->path : ""; - size_t a_path_len = strlen(a_path), b_path_len = strlen(b_path); - if (a_path_len < b_path_len) - return -1; - if (a_path_len > b_path_len) - return 1; - - // done with specificity, now on to identicalness - if (a->language < b->language) - return -1; - if (a->language > b->language) - return +1; - int cmp = strcmp(a_path, b_path); - if (cmp != 0) return cmp; - if (ap->index < bp->index) - return -1; - if (ap->index > bp->index) - return +1; - return 0; - -} - -static int config_part_qsort_cmp(const void *av, const void *bv) { - return config_part_cmp(av, bv); -} - -static char *config_read_string(Ted *ted, ConfigReader *cfg, char **ptext) { +static char *config_read_string(Ted *ted, ConfigReader *reader, char **ptext) { char *p; - u32 start_line = cfg->line_number; + u32 start_line = reader->line_number; char delimiter = **ptext; char *start = *ptext + 1; char *str = NULL; @@ -672,19 +458,19 @@ static char *config_read_string(Ted *ted, ConfigReader *cfg, char **ptext) { case '\0': goto null; default: - config_err(cfg, "Unrecognized escape sequence: '\\%c'.", *p); + config_err(reader, "Unrecognized escape sequence: '\\%c'.", *p); *ptext += strlen(*ptext); arr_clear(str); return NULL; } break; case '\n': - ++cfg->line_number; + ++reader->line_number; break; case '\0': null: - cfg->line_number = start_line; - config_err(cfg, "String doesn't end."); + reader->line_number = start_line; + config_err(reader, "String doesn't end."); *ptext += strlen(*ptext); arr_clear(str); return NULL; @@ -702,7 +488,7 @@ static char *config_read_string(Ted *ted, ConfigReader *cfg, char **ptext) { return s; } -static void settings_load_bg_shader(Ted *ted, Settings **applicable_settings, const char *bg_shader_text) { +static void settings_load_bg_shader(Ted *ted, Config *cfg, const char *bg_shader_text) { char vshader[8192] ; strbuf_printf(vshader, "attribute vec2 v_pos;\n\ OUT vec2 t_pos;\n\ @@ -748,19 +534,12 @@ uniform sampler2D t_texture;\n\ glBufferData(GL_ARRAY_BUFFER, sizeof buffer_data, buffer_data, GL_STATIC_DRAW); glVertexAttribPointer(v_pos, 2, GL_FLOAT, 0, 2 * sizeof(float), 0); glEnableVertexAttribArray(v_pos); - - GlRcSAB *bg_shader = gl_rc_sab_new(shader, array, buffer); - bg_shader->ref_count = arr_len(applicable_settings); - arr_foreach_ptr(applicable_settings, Settings *, psettings) { - Settings *settings = *psettings; - // decrease refcount on previous shader - gl_rc_sab_decref(&settings->bg_shader); - settings->bg_shader = bg_shader; - } + + cfg->settings.bg_shader = gl_rc_sab_new(shader, array, buffer); } -static void settings_load_bg_texture(Ted *ted, Settings **applicable_settings, const char *path) { +static void settings_load_bg_texture(Ted *ted, Config *cfg, const char *path) { char expanded[TED_PATH_MAX]; get_config_path(ted, expanded, sizeof expanded, path); @@ -769,38 +548,14 @@ static void settings_load_bg_texture(Ted *ted, Settings **applicable_settings, c ted_error(ted, "Couldn't load image %s", path); return; } - - GlRcTexture *bg_texture = gl_rc_texture_new(texture); - bg_texture->ref_count = arr_len(applicable_settings); - arr_foreach_ptr(applicable_settings, Settings *, psettings) { - Settings *settings = *psettings; - // decrease refcount on previous texture - gl_rc_texture_decref(&settings->bg_texture); - settings->bg_texture = bg_texture; - } - + + cfg->settings.bg_texture = gl_rc_texture_new(texture); } -// reads a single "line" of the config file, but it may include a multiline string, -// so it may read multiple lines. -// applicable_settings is a dynamic array of all settings objects to update -static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, const ConfigPart *part, char **pline) { - char *line = *pline; - Ted *ted = cfg->ted; - - char *newline = strchr(line, '\n'); - if (!newline) { - config_err(cfg, "No newline at end of file?"); - *pline += strlen(*pline); - return; - } - - if (newline) *newline = '\0'; - char *carriage_return = strchr(line, '\r'); - if (carriage_return) *carriage_return = '\0'; - *pline = newline + 1; - - if (part->section == 0) { +// NOTE: for multi-line strings, this will read from fp (otherwise it won't) +static void config_parse_line(ConfigReader *reader, Config *cfg, char *line, FILE *fp) { + Ted *ted = reader->ted; + if (reader->section == 0) { // there was an error reading this section. don't bother with anything else. return; } @@ -813,7 +568,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, char *equals = strchr(line, '='); if (!equals) { - config_err(cfg, "Invalid line syntax. " + config_err(reader, "Invalid line syntax. " "Lines should either look like [section-name] or key = value"); return; } @@ -831,13 +586,13 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, } } if (key[0] == '\0') { - config_err(cfg, "Empty property name. This line should look like: key = value"); + config_err(reader, "Empty property name. This line should look like: key = value"); return; } - switch (part->section) { + switch (reader->section) { case SECTION_NONE: - config_err(cfg, "Line outside of any section." + config_err(reader, "Line outside of any section." "Try putting a section header, e.g. [keyboard] before this line?"); break; case SECTION_COLORS: { @@ -845,11 +600,9 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, if (setting != COLOR_UNKNOWN) { u32 color = 0; if (color_from_str(value, &color)) { - arr_foreach_ptr(applicable_settings, Settings *, psettings) { - (*psettings)->colors[setting] = color; - } + config_set_color(cfg, setting, color); } else { - config_err(cfg, "'%s' is not a valid color. Colors should look like #rgb, #rgba, #rrggbb, or #rrggbbaa.", value); + config_err(reader, "'%s' is not a valid color. Colors should look like #rgb, #rgba, #rrggbb, or #rrggbbaa.", value); } } else { // don't actually produce this error. @@ -861,7 +614,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, } break; case SECTION_KEYBOARD: { // lines like Ctrl+Down = 10 :down - KeyCombo key_combo = config_parse_key_combo(cfg, key); + KeyCombo key_combo = config_parse_key_combo(reader, key); KeyAction action = {0}; action.key_combo = key_combo; CommandArgument argument = { @@ -875,20 +628,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, value = endp; } else if (*value == '"' || *value == '`') { // string argument - - // restore newline to handle multi-line strings - // a little bit hacky oh well - *newline = '\n'; - argument.string = config_read_string(ted, cfg, &value); - - newline = strchr(value, '\n'); - if (!newline) { - config_err(cfg, "No newline at end of file?"); - *pline += strlen(*pline); - return; - } - *newline = '\0'; - *pline = newline + 1; + argument.string = config_read_string(ted, reader, value, fp); } while (isspace(*value)) ++value; // skip past space following argument if (*value == ':') { @@ -898,32 +638,30 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, action.command = command; action.argument = argument; } else { - config_err(cfg, "Unrecognized command %s", value); + config_err(reader, "Unrecognized command %s", value); } } else { - config_err(cfg, "Expected ':' for key action. This line should look something like: %s = :command.", key); + config_err(reader, "Expected ':' for key action. This line should look something like: %s = :command.", key); } - arr_foreach_ptr(applicable_settings, Settings *, psettings) { - Settings *settings = *psettings; - bool have = false; - // check if we already have an action for this key combo - arr_foreach_ptr(settings->key_actions, KeyAction, act) { - if (act->key_combo.value == key_combo.value) { - *act = action; - have = true; - break; - } + Settings *settings = &cfg->settings; + bool have = false; + // check if we already have an action for this key combo + arr_foreach_ptr(settings->key_actions, KeyAction, act) { + if (act->key_combo.value == key_combo.value) { + *act = action; + have = true; + break; } - // if this is a new key combo, add an element to the key_actions array - if (!have) - arr_add(settings->key_actions, action); } + // if this is a new key combo, add an element to the key_actions array + if (!have) + arr_add(settings->key_actions, action); } break; case SECTION_EXTENSIONS: { Language lang = language_from_str(key); if (lang == LANG_NONE) { - config_err(cfg, "Invalid programming language: %s.", key); + config_err(reader, "Invalid programming language: %s.", key); } else { char *exts = calloc(1, strlen(value) + 1); char *dst = exts; @@ -932,32 +670,30 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, if (!isspace(*src)) *dst++ = *src; *dst = 0; - arr_foreach_ptr(applicable_settings, Settings *, psettings) { - Settings *settings = *psettings; - // remove old extensions - u32 *indices = NULL; - arr_foreach_ptr(settings->language_extensions, LanguageExtension, ext) { - if (ext->language == lang) { - arr_add(indices, (u32)(ext - settings->language_extensions)); - } - } - for (u32 i = 0; i < arr_len(indices); ++i) - arr_remove(settings->language_extensions, indices[i] - i); - arr_free(indices); - - char *p = exts; - while (*p) { - while (*p == ',') - ++p; - if (*p == '\0') - break; - size_t len = strcspn(p, ","); - LanguageExtension *ext = arr_addp(settings->language_extensions); - ext->language = lang; - memcpy(ext->extension, p, len); - p += len; + Settings *settings = &cfg->settings; + // remove old extensions + u32 *indices = NULL; + arr_foreach_ptr(settings->language_extensions, LanguageExtension, ext) { + if (ext->language == lang) { + arr_add(indices, (u32)(ext - settings->language_extensions)); } } + for (u32 i = 0; i < arr_len(indices); ++i) + arr_remove(settings->language_extensions, indices[i] - i); + arr_free(indices); + + char *p = exts; + while (*p) { + while (*p == ',') + ++p; + if (*p == '\0') + break; + size_t len = strcspn(p, ","); + LanguageExtension *ext = arr_addp(settings->language_extensions); + ext->language = lang; + memcpy(ext->extension, p, len); + p += len; + } free(exts); } } break; @@ -979,20 +715,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, } if (value[0] == '"' || value[0] == '`') { - // restore newline to handle multi-line strings - // a little bit hacky oh well - *newline = '\n'; - - char *string = config_read_string(ted, cfg, &value); - - newline = strchr(value, '\n'); - if (!newline) { - config_err(cfg, "No newline at end of file?"); - *pline += strlen(*pline); - return; - } - *newline = '\0'; - *pline = newline + 1; + char *string = config_read_string(ted, reader, value, fp); if (string) value = string; } @@ -1010,71 +733,68 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, if (!setting_any) { if (streq(key, "bg-shader")) - settings_load_bg_shader(ted, applicable_settings, value); + settings_load_bg_shader(ted, cfg, value); else if (streq(key, "bg-texture")) - settings_load_bg_texture(ted, applicable_settings, value); + settings_load_bg_texture(ted, cfg, value); // it's probably a bad idea to error on unrecognized settings // because if we ever remove a setting in the future // everyone will get errors break; } - arr_foreach_ptr(applicable_settings, Settings *, psettings) { - Settings *settings = *psettings; - if (part->context.language != 0 && !setting_any->per_language) { - config_err(cfg, "Setting %s cannot be controlled for individual languages.", key); - break; - } - - switch (setting_any->type) { - case SETTING_BOOL: { - const SettingBool *setting = &setting_any->u._bool; - if (is_bool) - setting_bool_set(settings, setting, boolean); - else - config_err(cfg, "Invalid %s: %s. This should be yes, no, on, or off.", setting->name, value); - } break; - case SETTING_U8: { - const SettingU8 *setting = &setting_any->u._u8; - if (is_integer && integer >= setting->min && integer <= setting->max) - setting_u8_set(settings, setting, (u8)integer); - else - config_err(cfg, "Invalid %s: %s. This should be an integer from %u to %u.", setting->name, value, setting->min, setting->max); - } break; - case SETTING_U16: { - const SettingU16 *setting = &setting_any->u._u16; - if (is_integer && integer >= setting->min && integer <= setting->max) - setting_u16_set(settings, setting, (u16)integer); - else - config_err(cfg, "Invalid %s: %s. This should be an integer from %u to %u.", setting->name, value, setting->min, setting->max); - } break; - case SETTING_U32: { - const SettingU32 *setting = &setting_any->u._u32; - if (is_integer && integer >= setting->min && integer <= setting->max) - setting_u32_set(settings, setting, (u32)integer); - else - config_err(cfg, "Invalid %s: %s. This should be an integer from %" PRIu32 " to %" PRIu32 ".", - setting->name, value, setting->min, setting->max); - } break; - case SETTING_FLOAT: { - const SettingFloat *setting = &setting_any->u._float; - if (is_floating && floating >= setting->min && floating <= setting->max) - setting_float_set(settings, setting, (float)floating); - else - config_err(cfg, "Invalid %s: %s. This should be a number from %g to %g.", setting->name, value, setting->min, setting->max); - } break; - case SETTING_STRING: { - const SettingString *setting = &setting_any->u._string; - setting_string_set(settings, setting, value); - } break; - case SETTING_KEY_COMBO: { - const SettingKeyCombo *setting = &setting_any->u._key; - KeyCombo combo = config_parse_key_combo(cfg, value); - if (combo.value) { - setting_key_combo_set(settings, setting, combo); - } - } break; + if (cfg->language != 0 && !setting_any->per_language) { + config_err(reader, "Setting %s cannot be controlled for individual languages.", key); + break; + } + + switch (setting_any->type) { + case SETTING_BOOL: { + const SettingBool *setting = &setting_any->u._bool; + if (is_bool) + config_set_bool(cfg, setting, boolean); + else + config_err(reader, "Invalid %s: %s. This should be yes, no, on, or off.", setting->name, value); + } break; + case SETTING_U8: { + const SettingU8 *setting = &setting_any->u._u8; + if (is_integer && integer >= setting->min && integer <= setting->max) + config_set_u8(cfg, setting, (u8)integer); + else + config_err(reader, "Invalid %s: %s. This should be an integer from %u to %u.", setting->name, value, setting->min, setting->max); + } break; + case SETTING_U16: { + const SettingU16 *setting = &setting_any->u._u16; + if (is_integer && integer >= setting->min && integer <= setting->max) + config_set_u16(cfg, setting, (u16)integer); + else + config_err(reader, "Invalid %s: %s. This should be an integer from %u to %u.", setting->name, value, setting->min, setting->max); + } break; + case SETTING_U32: { + const SettingU32 *setting = &setting_any->u._u32; + if (is_integer && integer >= setting->min && integer <= setting->max) + config_set_u32(cfg, setting, (u32)integer); + else + config_err(reader, "Invalid %s: %s. This should be an integer from %" PRIu32 " to %" PRIu32 ".", + setting->name, value, setting->min, setting->max); + } break; + case SETTING_FLOAT: { + const SettingFloat *setting = &setting_any->u._float; + if (is_floating && floating >= setting->min && floating <= setting->max) + config_set_float(cfg, setting, (float)floating); + else + config_err(reader, "Invalid %s: %s. This should be a number from %g to %g.", setting->name, value, setting->min, setting->max); + } break; + case SETTING_STRING: { + const SettingString *setting = &setting_any->u._string; + config_set_string(cfg, setting, value); + } break; + case SETTING_KEY_COMBO: { + const SettingKeyCombo *setting = &setting_any->u._key; + KeyCombo combo = config_parse_key_combo(reader, value); + if (combo.value) { + config_set_key_combo(cfg, setting, combo); } + } break; } } break; @@ -1090,154 +810,153 @@ static int key_action_qsort_cmp_combo(const void *av, const void *bv) { return 0; } -void config_parse(Ted *ted, ConfigPart **pparts) { - config_init_settings(); +static void config_read_file(Ted *ted, const char *cfg_path, const char ***include_stack) { + // check for, e.g. %include ted.cfg inside ted.cfg + arr_foreach_ptr(*include_stack, const char *, p_include) { + if (streq(cfg_path, *p_include)) { + char text[1024]; + strbuf_cpy(text, "%include loop in config files: "); + strbuf_cat(text, (*include_stack)[0]); + for (u32 i = 1; i < arr_len(*include_stack); ++i) { + if (i > 1) + strbuf_cat(text, ", which"); + strbuf_catf(text, " includes %s", (*include_stack)[i]); + } + if (arr_len(*include_stack) > 1) + strbuf_cat(text, ", which"); + strbuf_catf(text, " includes %s", cfg_path); + ted_error(ted, "%s", text); + return; + } + } + arr_add(*include_stack, cfg_path); - ConfigReader cfg_reader = { + FILE *fp = fopen(cfg_path, "rb"); + if (!fp) { + ted_error(ted, "Couldn't open config file %s: %s.", cfg_path, strerror(errno)); + return; + } + + + ConfigReader reader_data = { .ted = ted, - .filename = NULL, + .filename = cfg_path, .line_number = 1, .error = false }; - ConfigReader *cfg = &cfg_reader; - + ConfigReader *reader = &reader_data; - ConfigPart *const parts = *pparts; - qsort(parts, arr_len(parts), sizeof *parts, config_part_qsort_cmp); + Config *cfg = NULL; - const char **paths = NULL; - Language *languages = NULL; - arr_add(languages, 0); - // find all paths and languages referenced in config files - arr_foreach_ptr(parts, ConfigPart, part) { - bool already_have = false; - if (part->context.path) { - for (u32 i = 0; i < arr_len(paths); ++i) { - if (paths_eq(paths[i], part->context.path)) { - already_have = true; - break; - } - } - if (!already_have) - arr_add(paths, part->context.path); - } - already_have = false; - for (u32 i = 0; i < arr_len(languages); ++i) { - if (languages[i] == part->context.language) { - already_have = true; - break; - } - } - if (!already_have) - arr_add(languages, part->context.language); - } - arr_foreach_ptr(languages, Language, lang) { - // pathless settings - { - Settings *settings = arr_addp(ted->all_settings); - settings->context.language = *lang; + char line[4096] = {0}; + while (fgets(line, sizeof line, fp)) { + char *newline = strchr(line, '\n'); + if (!newline && !feof(fp)) { + config_err(reader, "Line is too long."); + break; } - arr_foreach_ptr(paths, const char *, path) { - Settings *settings = arr_addp(ted->all_settings); - settings->context.language = *lang; - settings->context.path = str_dup(*path); - } - } - arr_free(paths); - arr_free(languages); - - arr_foreach_ptr(parts, ConfigPart, part) { - cfg->filename = part->file; - cfg->line_number = part->line; - arr_add(part->text, '\0'); // null termination - char *line = part->text; - while (*line) { - Settings **applicable_settings = NULL; - arr_foreach_ptr(ted->all_settings, Settings, settings) { - if (context_is_parent(&part->context, &settings->context)) { - arr_add(applicable_settings, settings); + if (newline) *newline = '\0'; + char *carriage_return = strchr(line, '\r'); + if (carriage_return) *carriage_return = '\0'; + + if (line[0] == '[') { + // a new section! + #define SECTION_HEADER_HELP "Section headers should look like this: [(path//)(language.)section-name]" + char path[TED_PATH_MAX]; path[0] = '\0'; + char *closing = strchr(line, ']'); + if (!closing) { + config_err(reader, "Unmatched [. " SECTION_HEADER_HELP); + return false; + } else if (closing[1] != '\0') { + config_err(reader, "Text after section. " SECTION_HEADER_HELP); + return false; + } else { + *closing = '\0'; + char *section = line + 1; + char *path_end = strstr(section, "//"); + if (path_end) { + size_t path_len = (size_t)(path_end - section); + path[0] = '\0'; + // expand ~ + if (section[0] == '~') { + str_cpy(path, sizeof path, ted->home); + ++section; + --path_len; + } + strn_cat(path, sizeof path, section, path_len); + #if _WIN32 + // replace forward slashes with backslashes + for (char *p = path; *p; ++p) + if (*p == '/') + *p = '\\'; + #endif + cfg->path = str_dup(path); + section = path_end + 2; + } + + char *dot = strchr(section, '.'); + + if (dot) { + *dot = '\0'; + Language language = cfg->language = language_from_str(section); + if (!language) { + config_err(reader, "Unrecognized language: %s.", section); + } + section = dot + 1; + } + + if (streq(section, "keyboard")) { + reader->section = SECTION_KEYBOARD; + } else if (streq(section, "colors")) { + reader->section = SECTION_COLORS; + } else if (streq(section, "core")) { + reader->section = SECTION_CORE; + } else if (streq(section, "extensions")) { + if (cfg->language != 0 || cfg->path) { + config_err(reader, "Extensions section cannot be language- or path-specific."); + return; + } + reader->section = SECTION_EXTENSIONS; + } else { + config_err(reader, "Unrecognized section: [%s].", section); } } - config_parse_line(cfg, applicable_settings, part, &line); - arr_free(applicable_settings); - - if (cfg->error) break; - - ++cfg->line_number; - } - - - } - - arr_foreach_ptr(ted->all_settings, Settings, s) { - SettingsContext *ctx = &s->context; - if (ctx->language == 0 && (!ctx->path || !*ctx->path)) { - ted->default_settings = s; - break; + } else if (line[0] == '%') { + if (str_has_prefix(line, "%include ")) { + char included[TED_PATH_MAX]; + char expanded[TED_PATH_MAX]; + strbuf_cpy(included, line + strlen("%include ")); + while (*included && isspace(included[strlen(included) - 1])) + included[strlen(included) - 1] = '\0'; + get_config_path(ted, expanded, sizeof expanded, included); + config_read_file(ted, expanded, include_stack); + } + } else if (cfg) { + config_parse_line(reader, cfg, line, fp); + } else { + const char *p = line; + while (isspace(*p)) ++p; + if (*p == '\0' || *p == '#') { + // blank line + } else { + config_err(reader, "Config has text before first section header."); + } } + ++reader->line_number; } - arr_foreach_ptr(parts, ConfigPart, part) { - config_part_free(part); - } - - arr_clear(*pparts); - - arr_foreach_ptr(ted->all_settings, Settings, s) { - // sort key_actions by key_combo. - arr_qsort(s->key_actions, key_action_qsort_cmp_combo); - } -} - -static int gluint_cmp(const void *av, const void *bv) { - const GLuint *ap = av, *bp = bv; - GLuint a = *ap, b = *bp; - if (a < b) - return -1; - if (a > b) - return 1; - return 0; -} - -static void gluint_eliminate_duplicates(GLuint **arr) { - arr_qsort(*arr, gluint_cmp); - - GLuint *start = *arr; - GLuint *end = *arr + arr_len(*arr); - GLuint *out = start; - const GLuint *in = start; - - while (in < end) { - if (in == start || in[0] != in[-1]) - *out++ = *in; - ++in; - } - size_t count = (size_t)(out - *arr); - arr_set_len(*arr, count); -} - -static void settings_free(Settings *settings) { - context_free(&settings->context); - arr_free(settings->language_extensions); - gl_rc_sab_decref(&settings->bg_shader); - gl_rc_texture_decref(&settings->bg_texture); - arr_free(settings->key_actions); - for (size_t i = 0; i < arr_count(settings_string); i++) { - const SettingString *s = &settings_string[i]; - RcStr **rc = (RcStr **)((char *)settings + ((char *)s->control - (char *)&settings_zero)); - rc_str_decref(rc); - } + if (ferror(fp)) + ted_error(ted, "Error reading %s.", cfg_path); + fclose(fp); + arr_remove_last(*include_stack); } -void config_free(Ted *ted) { - arr_foreach_ptr(ted->all_settings, Settings, settings) { - settings_free(settings); +void config_free_all(Ted *ted) { + arr_foreach_ptr(ted->all_configs, Config, cfg) { + config_free(cfg); } - - - arr_clear(ted->all_settings); - + arr_clear(ted->all_configs); for (u32 i = 0; i < ted->nstrings; ++i) { free(ted->strings[i]); ted->strings[i] = NULL; @@ -1,4 +1,8 @@ /* +TODO: +- switch back to starting file after rename +- put signature help at top if cursor is near bottom +- .editorconfig? see https://editorconfig.org/ FUTURE FEATURES: - autodetect indentation (tabs vs spaces) - custom file/build command associations @@ -1261,7 +1265,7 @@ int main(int argc, char **argv) { buffer_free(ted->build_buffer); buffer_free(ted->argument_buffer); ted_free_fonts(ted); - config_free(ted); + config_free_all(ted); macros_free(ted); free(ted); #if _WIN32 diff --git a/ted-internal.h b/ted-internal.h index fe377a4..09ba7c9 100644 --- a/ted-internal.h +++ b/ted-internal.h @@ -99,7 +99,6 @@ typedef struct { /// NOTE: to add more options to ted, add fields here, /// and change the settings_<type> global constant near the top of config.c struct Settings { - SettingsContext context; u32 colors[COLOR_COUNT]; float cursor_blink_time_on, cursor_blink_time_off; float hover_time; @@ -173,9 +172,12 @@ struct Settings { KeyAction *key_actions; }; -/// This structure is used temporarily when loading settings -/// It's needed because we want more specific contexts to be dealt with last. -typedef struct ConfigPart ConfigPart; +typedef struct { + Language language; + char *path; + Settings settings; + bool settings_set[sizeof (Settings)]; +} Config; typedef struct EditNotifyInfo { EditNotify fn; @@ -284,8 +286,7 @@ struct Ted { /// the old active buffer needs to be restored. that's what this stores. TextBuffer *prev_active_buffer; Node *active_node; - /// dynamic array of Settings. use Settings.context to figure out which one to use. - Settings *all_settings; + Config *all_configs; /// settings to use when no buffer is open Settings *default_settings; float window_width, window_height; @@ -498,26 +499,8 @@ void command_init(void); void command_execute_ex(Ted *ted, Command c, const CommandArgument *argument, const CommandContext *context); // === config.c === -/// first, we read all config files, then we parse them. -/// this is because we want less specific settings (e.g. settings applied -/// to all languages instead of one particular language) to be applied first, -/// then more specific settings are based off of those. -/// -/// EXAMPLE: -/// ``` -/// ---config file 1--- -/// [Javascript.core] -/// syntax-highlighting = off -/// (inherits tab-width = 4) -/// [CSS.core] -/// tab-width = 2 (overrides tab-width = 4) -/// ---config file 2--- -/// [core] -/// tab-width = 4 -/// ``` -void config_read(Ted *ted, ConfigPart **pparts, const char *filename); -void config_parse(Ted *ted, ConfigPart **pparts); -void config_free(Ted *ted); +void config_read(Ted *ted, const char *filename); +void config_free_all(Ted *ted); /// how well does this settings context fit the given path and language? /// the context with the highest score will be chosen. long context_score(const char *path, Language lang, const SettingsContext *context); |