From ab65fe136f7a8739b78aaa4801a9fb761a301417 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 29 Jul 2022 14:29:36 -0400 Subject: per-language settings also fixed ctrl+/ in go --- autocomplete.c | 10 +- buffer.c | 13 +- build.c | 2 +- command.c | 2 +- config.c | 684 ++++++++++++++++++++++++++++++++++----------------------- find.c | 4 +- main.c | 41 ++-- menu.c | 8 +- node.c | 2 +- session.c | 4 +- syntax.c | 1 + tags.c | 6 +- ted.c | 9 +- ted.cfg | 3 +- ted.h | 5 +- ui.c | 18 +- 16 files changed, 484 insertions(+), 328 deletions(-) diff --git a/autocomplete.c b/autocomplete.c index fb8da92..23523bd 100644 --- a/autocomplete.c +++ b/autocomplete.c @@ -1,5 +1,5 @@ - #define AUTOCOMPLETE_NCOMPLETIONS 10 // max # of completions to show +#define AUTOCOMPLETE_NCOMPLETIONS 10 // max # of completions to show // get the thing to be completed (and what buffer it's in) // returns false if this is a read only buffer or something @@ -80,16 +80,16 @@ static void autocomplete_open(Ted *ted) { } static void autocomplete_frame(Ted *ted) { - Settings const *settings = &ted->settings; - u32 const *colors = settings->colors; - float const padding = settings->padding; char *start; TextBuffer *buffer; if (autocomplete_get(ted, &start, &buffer)) { Font *font = ted->font; float char_height = text_font_char_height(font); - + Settings const *settings = buffer_settings(buffer); + u32 const *colors = settings->colors; + float const padding = settings->padding; + char *completions[AUTOCOMPLETE_NCOMPLETIONS]; size_t ncompletions = tags_beginning_with(ted, start, completions, arr_count(completions)); float menu_width = 400, menu_height = (float)ncompletions * char_height + 2 * padding; diff --git a/buffer.c b/buffer.c index e1498ef..a4eeab6 100644 --- a/buffer.c +++ b/buffer.c @@ -243,16 +243,11 @@ static inline Font *buffer_font(TextBuffer *buffer) { return buffer->ted->font; } -// Get the settings used for this buffer. -static inline Settings const *buffer_settings(TextBuffer *buffer) { - return &buffer->ted->settings; -} - // what programming language is this? Language buffer_language(TextBuffer *buffer) { if (buffer->manual_language >= 1 && buffer->manual_language <= LANG_COUNT) return (Language)(buffer->manual_language - 1); - Settings const *settings = buffer_settings(buffer); + Settings const *settings = buffer->ted->settings; char const *filename = buffer->filename; if (!filename) return LANG_NONE; @@ -277,6 +272,12 @@ Language buffer_language(TextBuffer *buffer) { return LANG_NONE; } +// Get the settings used for this buffer. +Settings *buffer_settings(TextBuffer *buffer) { + return &buffer->ted->settings_by_language[buffer_language(buffer)]; +} + + // NOTE: this string will be invalidated when the line is edited!!! // only use it briefly!! static String32 buffer_get_line(TextBuffer *buffer, u32 line_number) { diff --git a/build.c b/build.c index cca1700..3f4cc57 100644 --- a/build.c +++ b/build.c @@ -81,7 +81,7 @@ static void build_start(Ted *ted) { bool cargo = false, make = false; strbuf_cpy(ted->build_dir, ted->cwd); - Settings *settings = &ted->settings; + Settings *settings = ted->active_buffer ? buffer_settings(ted->active_buffer) : ted->settings; char *command = settings->build_default_command; diff --git a/command.c b/command.c index 8c1bf94..3e10a93 100644 --- a/command.c +++ b/command.c @@ -31,7 +31,7 @@ char const *arg_get_string(Ted *ted, i64 argument) { void command_execute(Ted *ted, Command c, i64 argument) { TextBuffer *buffer = ted->active_buffer; Node *node = ted->active_node; - Settings *settings = &ted->settings; + Settings *settings = buffer ? buffer_settings(buffer) : ted->settings; switch (c) { diff --git a/config.c b/config.c index 49a0499..aa09444 100644 --- a/config.c +++ b/config.c @@ -16,9 +16,9 @@ typedef enum { } Section; // all worth it for the -Wformat warnings -#define config_err(cfg, ...) snprintf((cfg)->ted->error, sizeof (cfg)->ted->error - 1, "%s:%u: ", (cfg)->filename, (cfg)->line_number), \ +#define config_err(cfg, ...) do { snprintf((cfg)->ted->error, sizeof (cfg)->ted->error - 1, "%s:%u: ", (cfg)->filename, (cfg)->line_number), \ snprintf((cfg)->ted->error + strlen((cfg)->ted->error), sizeof (cfg)->ted->error - 1 - strlen((cfg)->ted->error), __VA_ARGS__), \ - (cfg)->error = true + (cfg)->error = true; } while (0) typedef struct { Ted *ted; @@ -143,7 +143,82 @@ static u32 config_parse_key_combo(ConfigReader *cfg, char const *str) { return (u32)scancode << 3 | modifier; } -void config_read(Ted *ted, char const *filename) { + +// all the "control" pointers here are relative to a NULL Settings object. +typedef struct { + char const *name; + const bool *control; + bool per_language; // allow per-language control +} OptionBool; +typedef struct { + char const *name; + const u8 *control; + u8 min, max; + bool per_language; +} OptionU8; +typedef struct { + char const *name; + const float *control; + float min, max; + bool per_language; +} OptionFloat; +typedef struct { + char const *name; + const u16 *control; + u16 min, max; + bool per_language; +} OptionU16; +typedef struct { + char const *name; + const char *control; + size_t buf_size; + bool per_language; +} OptionString; + +typedef enum { + OPTION_BOOL = 1, + OPTION_U8, + OPTION_U16, + OPTION_FLOAT, + OPTION_STRING +} OptionType; +typedef struct { + OptionType type; + const char *name; + bool per_language; + union { + OptionU8 _u8; + OptionBool _bool; + OptionU16 _u16; + OptionFloat _float; + OptionString _string; + } u; +} OptionAny; + +static void option_bool_set(Settings *settings, const OptionBool *opt, bool value) { + *(bool *)((char *)settings + (size_t)opt->control) = value; +} +static void option_u8_set(Settings *settings, const OptionU8 *opt, u8 value) { + if (value >= opt->min && value <= opt->max) + *(u8 *)((char *)settings + (size_t)opt->control) = value; +} +static void option_u16_set(Settings *settings, const OptionU16 *opt, u16 value) { + if (value >= opt->min && value <= opt->max) + *(u16 *)((char *)settings + (size_t)opt->control) = value; +} +static void option_float_set(Settings *settings, const OptionFloat *opt, float value) { + if (value >= opt->min && value <= opt->max) + *(float *)((char *)settings + (size_t)opt->control) = value; +} +static void option_string_set(Settings *settings, const OptionString *opt, const char *value) { + char *control = (char *)settings + (size_t)opt->control; + str_cpy(control, opt->buf_size, value); +} + +// two passes are done +// pass 0 reads global settings +// pass 1 reads language-specific settings +void config_read(Ted *ted, char const *filename, int pass) { ConfigReader cfg_reader = { .ted = ted, .filename = filename, @@ -151,317 +226,380 @@ void config_read(Ted *ted, char const *filename) { .error = false }; ConfigReader *cfg = &cfg_reader; - Settings *settings = &ted->settings; + Settings *settings = ted->settings; - typedef struct { - char const *name; - bool *control; - } OptionBool; - typedef struct { - char const *name; - u8 *control, min, max; - } OptionU8; - typedef struct { - char const *name; - float *control, min, max; - } OptionFloat; - typedef struct { - char const *name; - u16 *control, min, max; - } OptionU16; - typedef struct { - char const *name; - char *control; - size_t buf_size; - } OptionString; // core options // (these go at the start so they don't need to be re-computed each time) + const Settings *nullset = NULL; OptionBool const options_bool[] = { - {"auto-indent", &settings->auto_indent}, - {"auto-add-newline", &settings->auto_add_newline}, - {"auto-reload", &settings->auto_reload}, - {"syntax-highlighting", &settings->syntax_highlighting}, - {"line-numbers", &settings->line_numbers}, - {"restore-session", &settings->restore_session}, - {"regenerate-tags-if-not-found", &settings->regenerate_tags_if_not_found}, + {"auto-indent", &nullset->auto_indent, true}, + {"auto-add-newline", &nullset->auto_add_newline, true}, + {"auto-reload", &nullset->auto_reload, true}, + {"syntax-highlighting", &nullset->syntax_highlighting, true}, + {"line-numbers", &nullset->line_numbers, true}, + {"restore-session", &nullset->restore_session, false}, + {"regenerate-tags-if-not-found", &nullset->regenerate_tags_if_not_found, true}, }; OptionU8 const options_u8[] = { - {"tab-width", &settings->tab_width, 1, 100}, - {"cursor-width", &settings->cursor_width, 1, 100}, - {"undo-save-time", &settings->undo_save_time, 1, 200}, - {"border-thickness", &settings->border_thickness, 1, 30}, - {"padding", &settings->padding, 0, 100}, - {"scrolloff", &settings->scrolloff, 1, 100}, - {"tags-max-depth", &settings->tags_max_depth, 1, 100}, - }; - OptionFloat const options_float[] = { - {"cursor-blink-time-on", &settings->cursor_blink_time_on, 0, 1000}, - {"cursor-blink-time-off", &settings->cursor_blink_time_off, 0, 1000}, + {"tab-width", &nullset->tab_width, 1, 100, true}, + {"cursor-width", &nullset->cursor_width, 1, 100, true}, + {"undo-save-time", &nullset->undo_save_time, 1, 200, true}, + {"border-thickness", &nullset->border_thickness, 1, 30, false}, + {"padding", &nullset->padding, 0, 100, false}, + {"scrolloff", &nullset->scrolloff, 1, 100, true}, + {"tags-max-depth", &nullset->tags_max_depth, 1, 100, false}, }; OptionU16 const options_u16[] = { - {"text-size", &settings->text_size, TEXT_SIZE_MIN, TEXT_SIZE_MAX}, - {"max-menu-width", &settings->max_menu_width, 10, U16_MAX}, - {"error-display-time", &settings->error_display_time, 0, U16_MAX}, + {"text-size", &nullset->text_size, TEXT_SIZE_MIN, TEXT_SIZE_MAX, true}, + {"max-menu-width", &nullset->max_menu_width, 10, U16_MAX, false}, + {"error-display-time", &nullset->error_display_time, 0, U16_MAX, false}, + }; + OptionFloat const options_float[] = { + {"cursor-blink-time-on", &nullset->cursor_blink_time_on, 0, 1000, true}, + {"cursor-blink-time-off", &nullset->cursor_blink_time_off, 0, 1000, true}, }; OptionString const options_string[] = { - {"build-default-command", settings->build_default_command, sizeof settings->build_default_command}, + {"build-default-command", nullset->build_default_command, sizeof nullset->build_default_command, true}, }; + + OptionAny all_options[1000] = {0}; + OptionAny *all_options_end = all_options; + for (size_t i = 0; i < arr_count(options_bool); ++i) { + OptionAny *opt = all_options_end++; + opt->type = OPTION_BOOL; + opt->name = options_bool[i].name; + opt->per_language = options_bool[i].per_language; + opt->u._bool = options_bool[i]; + } + for (size_t i = 0; i < arr_count(options_u8); ++i) { + OptionAny *opt = all_options_end++; + opt->type = OPTION_U8; + opt->name = options_u8[i].name; + opt->per_language = options_u8[i].per_language; + opt->u._u8 = options_u8[i]; + } + for (size_t i = 0; i < arr_count(options_u16); ++i) { + OptionAny *opt = all_options_end++; + opt->type = OPTION_U16; + opt->name = options_u16[i].name; + opt->per_language = options_u16[i].per_language; + opt->u._u16 = options_u16[i]; + } + for (size_t i = 0; i < arr_count(options_float); ++i) { + OptionAny *opt = all_options_end++; + opt->type = OPTION_FLOAT; + opt->name = options_float[i].name; + opt->per_language = options_float[i].per_language; + opt->u._float = options_float[i]; + } + for (size_t i = 0; i < arr_count(options_string); ++i) { + OptionAny *opt = all_options_end++; + opt->type = OPTION_STRING; + opt->name = options_string[i].name; + opt->per_language = options_string[i].per_language; + opt->u._string = options_string[i]; + } FILE *fp = fopen(filename, "rb"); - if (fp) { - int line_cap = 4096; - char *line = ted_malloc(ted, (size_t)line_cap); - if (line) { - Section section = SECTION_NONE; - - while (fgets(line, line_cap, fp)) { - char *newline = strchr(line, '\n'); - if (newline || feof(fp)) { - if (newline) *newline = '\0'; - char *carriage_return = strchr(line, '\r'); - if (carriage_return) *carriage_return = '\0'; + if (!fp) { + ted_seterr(ted, "Couldn't open config file %s.", filename); + return; + } + + char line[4096] = {0}; + int line_cap = sizeof line; + + Section section = SECTION_NONE; + Language language = LANG_NONE; + bool skip_section = false; + + while (fgets(line, line_cap, 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'; - // ok, we've now read a line. - switch (line[0]) { - case '#': // comment - case '\0': // blank line + // ok, we've now read a line. + switch (line[0]) { + case '#': // comment + case '\0': // blank line + break; + case '[': { // section header + #define SECTION_HEADER_HELP "Section headers should look like this: [section-name]" + char *closing = strchr(line, ']'); + if (!closing) { + config_err(cfg, "Unmatched [. " SECTION_HEADER_HELP); + } else if (closing[1] != '\0') { + config_err(cfg, "Text after section. " SECTION_HEADER_HELP); + } else { + *closing = '\0'; + char *section_name = line + 1; + char *dot = strchr(section_name, '.'); + + if (dot) { + *dot = '\0'; + language = language_from_str(section_name); + if (!language) { + config_err(cfg, "Unrecognized language: %s.", section_name); + break; // skip section name check + } + section_name = dot + 1; + } else { + language = 0; + } + + if (streq(section_name, "keyboard")) { + section = SECTION_KEYBOARD; + } else if (streq(section_name, "colors")) { + section = SECTION_COLORS; + } else if (streq(section_name, "core")) { + section = SECTION_CORE; + } else if (streq(section_name, "extensions")) { + section = SECTION_EXTENSIONS; + } else { + config_err(cfg, "Unrecognized section: [%s].", section_name); + break; + } + + skip_section = false; + if (language) { + switch (section) { + case SECTION_CORE: + case SECTION_COLORS: break; - case '[': { // section header - #define SECTION_HEADER_HELP "Section headers should look like this: [section-name]" - char *closing = strchr(line, ']'); - if (!closing) { - config_err(cfg, "Unmatched [. " SECTION_HEADER_HELP); - } else if (closing[1] != '\0') { - config_err(cfg, "Text after section. " SECTION_HEADER_HELP); - } else { - *closing = '\0'; - char *section_name = line + 1; - if (streq(section_name, "keyboard")) { - section = SECTION_KEYBOARD; - } else if (streq(section_name, "colors")) { - section = SECTION_COLORS; - } else if (streq(section_name, "core")) { - section = SECTION_CORE; - } else if (streq(section_name, "extensions")) { - section = SECTION_EXTENSIONS; + default: + config_err(cfg, "%s settings cannot be configured for individual languages.", + section_name); + break; + } + if (pass == 0) { + skip_section = true; + } + } else { + if (pass == 1) { + skip_section = true; + } + } + if (pass == 1) { + settings = &ted->settings_by_language[language]; + } + } + } break; + default: { + if (skip_section) break; + + char *equals = strchr(line, '='); + if (equals) { + char const *key = line; + *equals = '\0'; + char const *value = equals + 1; + while (isspace(*key)) ++key; + while (isspace(*value)) ++value; + if (equals != line) { + for (char *p = equals - 1; p > line; --p) { + // remove trailing spaces after key + if (isspace(*p)) *p = '\0'; + else break; + } + } + if (key[0] == '\0') { + config_err(cfg, "Empty property name. This line should look like: key = value"); + } else { + switch (section) { + case SECTION_NONE: + config_err(cfg, "Line outside of any section." + "Try putting a section header, e.g. [keyboard] before this line?"); + break; + case SECTION_COLORS: { + ColorSetting setting = color_setting_from_str(key); + if (setting != COLOR_UNKNOWN) { + u32 color = 0; + if (color_from_str(value, &color)) { + settings->colors[setting] = color; } else { - config_err(cfg, "Unrecognized section: [%s].", section_name); + config_err(cfg, "'%s' is not a valid color. Colors should look like #rgb, #rgba, #rrggbb, or #rrggbbaa.", value); } + } else { + config_err(cfg, "No such color option: %s", key); } } break; - default: { - char *equals = strchr(line, '='); - if (equals) { - char const *key = line; - *equals = '\0'; - char const *value = equals + 1; - while (isspace(*key)) ++key; - while (isspace(*value)) ++value; - if (equals != line) { - for (char *p = equals - 1; p > line; --p) { - // remove trailing spaces after key - if (isspace(*p)) *p = '\0'; - else break; + case SECTION_KEYBOARD: { + // lines like Ctrl+Down = 10 :down + u32 key_combo = config_parse_key_combo(cfg, key); + KeyAction *action = &ted->key_actions[key_combo]; + llong argument = 1; + if (isdigit(*value)) { + // read the argument + char *endp; + argument = strtoll(value, &endp, 10); + value = endp; + } else if (*value == '"') { + // string argument + int backslashes = 0; + char const *p; + for (p = value + 1; *p; ++p) { + bool done = false; + switch (*p) { + case '\\': + ++backslashes; + break; + case '"': + if (backslashes % 2 == 0) + done = true; + break; } + if (done) break; + } + if (!*p) { + config_err(cfg, "String doesn't end."); + break; + } + if (ted->nstrings < TED_MAX_STRINGS) { + char *str = strn_dup(value + 1, (size_t)(p - (value + 1))); + argument = ted->nstrings | ARG_STRING; + ted->strings[ted->nstrings++] = str; } - if (key[0] == '\0') { - config_err(cfg, "Empty property name. This line should look like: key = value"); + value = p + 1; + } + while (isspace(*value)) ++value; // skip past space following argument + if (*value == ':') { + // read the command + Command command = command_from_str(value + 1); + if (command != CMD_UNKNOWN) { + action->command = command; + action->argument = argument; + action->line_number = cfg->line_number; } else { - switch (section) { - case SECTION_NONE: - config_err(cfg, "Line outside of any section." - "Try putting a section header, e.g. [keyboard] before this line?"); + config_err(cfg, "Unrecognized command %s", value); + } + } else { + config_err(cfg, "Expected ':' for key action. This line should look something like: %s = :command.", key); + } + } break; + case SECTION_EXTENSIONS: { + Language lang = language_from_str(key); + if (lang == LANG_NONE) { + config_err(cfg, "Invalid programming language: %s.", key); + } else { + char *new_str = malloc(strlen(value) + 1); + if (!new_str) { + config_err(cfg, "Out of memory."); + } else { + char *dst = new_str; + // get rid of whitespace in extension list + for (char const *src = value; *src; ++src) + if (!isspace(*src)) + *dst++ = *src; + *dst = 0; + if (settings->language_extensions[lang]) + free(settings->language_extensions[lang]); + settings->language_extensions[lang] = new_str; + } + } + } break; + case SECTION_CORE: { + char const *endptr; + long long const integer = strtoll(value, (char **)&endptr, 10); + bool const is_integer = *endptr == '\0'; + double const floating = strtod(value, (char **)&endptr); + bool const is_floating = *endptr == '\0'; + bool is_bool = false; + bool boolean = false; + #define BOOL_HELP "(should be yes/no/on/off)" + if (streq(value, "yes") || streq(value, "on")) { + is_bool = true; + boolean = true; + } else if (streq(value, "no") || streq(value, "off")) { + is_bool = true; + boolean = false; + } + + // go through all options + bool recognized = false; + for (size_t i = 0; i < arr_count(all_options) && !recognized; ++i) { + OptionAny const *any = &all_options[i]; + if (any->type == 0) break; + if (streq(key, any->name)) { + recognized = true; + + if (language != 0 && !any->per_language) { + config_err(cfg, "Option %s cannot be controlled for individual languages.", key); break; - case SECTION_COLORS: { - ColorSetting setting = color_setting_from_str(key); - if (setting != COLOR_UNKNOWN) { - u32 color = 0; - if (color_from_str(value, &color)) { - settings->colors[setting] = color; - } else { - config_err(cfg, "'%s' is not a valid color. Colors should look like #rgb, #rgba, #rrggbb, or #rrggbbaa.", value); - } - } else { - config_err(cfg, "No such color option: %s", key); - } + } + + switch (any->type) { + case OPTION_BOOL: { + OptionBool const *option = &any->u._bool; + if (is_bool) + option_bool_set(settings, option, boolean); + else + config_err(cfg, "Invalid %s: %s. This should be yes, no, on, or off.", option->name, value); } break; - case SECTION_KEYBOARD: { - // lines like Ctrl+Down = 10 :down - u32 key_combo = config_parse_key_combo(cfg, key); - KeyAction *action = &ted->key_actions[key_combo]; - llong argument = 1; - if (isdigit(*value)) { - // read the argument - char *endp; - argument = strtoll(value, &endp, 10); - value = endp; - } else if (*value == '"') { - // string argument - int backslashes = 0; - char const *p; - for (p = value + 1; *p; ++p) { - bool done = false; - switch (*p) { - case '\\': - ++backslashes; - break; - case '"': - if (backslashes % 2 == 0) - done = true; - break; - } - if (done) break; - } - if (!*p) { - config_err(cfg, "String doesn't end."); - break; - } - if (ted->nstrings < TED_MAX_STRINGS) { - char *str = strn_dup(value + 1, (size_t)(p - (value + 1))); - argument = ted->nstrings | ARG_STRING; - ted->strings[ted->nstrings++] = str; - } - value = p + 1; - } - while (isspace(*value)) ++value; // skip past space following argument - if (*value == ':') { - // read the command - Command command = command_from_str(value + 1); - if (command != CMD_UNKNOWN) { - action->command = command; - action->argument = argument; - action->line_number = cfg->line_number; - } else { - config_err(cfg, "Unrecognized command %s", value); - } - } else { - config_err(cfg, "Expected ':' for key action. This line should look something like: %s = :command.", key); - } + case OPTION_U8: { + OptionU8 const *option = &any->u._u8; + if (is_integer && integer >= option->min && integer <= option->max) + option_u8_set(settings, option, (u8)integer); + else + config_err(cfg, "Invalid %s: %s. This should be an integer from %u to %u.", option->name, value, option->min, option->max); } break; - case SECTION_EXTENSIONS: { - Language lang = language_from_str(key); - if (lang == LANG_NONE) { - config_err(cfg, "Invalid programming language: %s.", key); - } else { - char *new_str = malloc(strlen(value) + 1); - if (!new_str) { - config_err(cfg, "Out of memory."); - } else { - char *dst = new_str; - // get rid of whitespace in extension list - for (char const *src = value; *src; ++src) - if (!isspace(*src)) - *dst++ = *src; - *dst = 0; - if (settings->language_extensions[lang]) - free(settings->language_extensions[lang]); - settings->language_extensions[lang] = new_str; - } - } + case OPTION_U16: { + OptionU16 const *option = &any->u._u16; + if (is_integer && integer >= option->min && integer <= option->max) + option_u16_set(settings, option, (u16)integer); + else + config_err(cfg, "Invalid %s: %s. This should be an integer from %u to %u.", option->name, value, option->min, option->max); } break; - case SECTION_CORE: { - char const *endptr; - long long const integer = strtoll(value, (char **)&endptr, 10); - bool const is_integer = *endptr == '\0'; - double const floating = strtod(value, (char **)&endptr); - bool const is_floating = *endptr == '\0'; - bool is_bool = false; - bool boolean = false; - #define BOOL_HELP "(should be yes/no/on/off)" - if (streq(value, "yes") || streq(value, "on")) { - is_bool = true; - boolean = true; - } else if (streq(value, "no") || streq(value, "off")) { - is_bool = true; - boolean = false; - } - - // go through all types of options - for (size_t i = 0; i < arr_count(options_bool); ++i) { - OptionBool const *option = &options_bool[i]; - if (streq(key, option->name)) { - if (is_bool) - *option->control = boolean; - else - config_err(cfg, "Invalid %s: %s. This should be yes, no, on, or off.", option->name, value); - } - } - - for (size_t i = 0; i < arr_count(options_u8); ++i) { - OptionU8 const *option = &options_u8[i]; - if (streq(key, option->name)) { - if (is_integer && integer >= option->min && integer <= option->max) - *option->control = (u8)integer; - else - config_err(cfg, "Invalid %s: %s. This should be an integer from %u to %u.", option->name, value, option->min, option->max); - } - } - - for (size_t i = 0; i < arr_count(options_u16); ++i) { - OptionU16 const *option = &options_u16[i]; - if (streq(key, option->name)) { - if (is_integer && integer >= option->min && integer <= option->max) - *option->control = (u16)integer; - else - config_err(cfg, "Invalid %s: %s. This should be an integer from %u to %u.", option->name, value, option->min, option->max); - } - } - - - for (size_t i = 0; i < arr_count(options_float); ++i) { - OptionFloat const *option = &options_float[i]; - if (streq(key, option->name)) { - if (is_floating && floating >= option->min && floating <= option->max) - *option->control = (float)floating; - else - config_err(cfg, "Invalid %s: %s. This should be a number from %g to %g.", option->name, value, option->min, option->max); - } - } - - for (size_t i = 0; i < arr_count(options_string); ++i) { - OptionString const *option = &options_string[i]; - if (streq(key, option->name)) { - if (strlen(value) >= option->buf_size) { - config_err(cfg, "%s is too long (length: %zu, maximum length: %zu).", key, strlen(value), option->buf_size - 1); - } else { - str_cpy(option->control, option->buf_size, value); - } - } + case OPTION_FLOAT: { + OptionFloat const *option = &any->u._float; + if (is_floating && floating >= option->min && floating <= option->max) + option_float_set(settings, option, (float)floating); + else + config_err(cfg, "Invalid %s: %s. This should be a number from %g to %g.", option->name, value, option->min, option->max); + } break; + case OPTION_STRING: { + OptionString const *option = &any->u._string; + if (strlen(value) >= option->buf_size) { + config_err(cfg, "%s is too long (length: %zu, maximum length: %zu).", key, strlen(value), option->buf_size - 1); + } else { + option_string_set(settings, option, value); } - } break; } } - } else { - config_err(cfg, "Invalid line syntax. " - "Lines should either look like [section-name] or key = value"); } } break; } - - if (cfg->error) break; - - ++cfg->line_number; - } else { - config_err(cfg, "Line is too long."); - break; } + } else { + config_err(cfg, "Invalid line syntax. " + "Lines should either look like [section-name] or key = value"); } + } break; } - free(line); - if (ferror(fp)) - ted_seterr(ted, "Error reading %s.", filename); - fclose(fp); - } else { - ted_seterr(ted, "Couldn't open file %s.", filename); + if (cfg->error) break; + + ++cfg->line_number; } + + + if (ferror(fp)) + ted_seterr(ted, "Error reading %s.", filename); + fclose(fp); } static void config_free(Ted *ted) { - Settings *settings = &ted->settings; for (u16 i = 0; i < LANG_COUNT; ++i) { - free(settings->language_extensions[i]); - settings->language_extensions[i] = NULL; + free(ted->settings_by_language[0].language_extensions[i]); + for (u16 l = 0; l < LANG_COUNT; ++l) { + // these are just aliases to settings_by_language[0].language_extensions[i] + // (you cant change language extensions on a per language basis. that would be weird.) + ted->settings_by_language[l].language_extensions[i] = NULL; + } } for (u32 i = 0; i < ted->nstrings; ++i) { free(ted->strings[i]); diff --git a/find.c b/find.c index d4a24c4..d5cdfce 100644 --- a/find.c +++ b/find.c @@ -70,7 +70,7 @@ static void find_free_pattern(Ted *ted) { static float find_menu_height(Ted *ted) { Font *font = ted->font; float char_height = text_font_char_height(font); - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; float const padding = settings->padding; float const border_thickness = settings->border_thickness; float const line_buffer_height = ted_line_buffer_height(ted); @@ -315,7 +315,7 @@ static void find_menu_frame(Ted *ted, Rect menu_bounds) { Font *font = ted->font, *font_bold = ted->font_bold; float const char_height = text_font_char_height(font); - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; float const padding = settings->padding; float const border_thickness = settings->border_thickness; u32 const *colors = settings->colors; diff --git a/main.c b/main.c index 63b7ad7..7a6731e 100644 --- a/main.c +++ b/main.c @@ -95,7 +95,7 @@ static void die(char const *fmt, ...) { static Rect error_box_rect(Ted *ted) { Font *font = ted->font; - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; float padding = settings->padding; float window_width = ted->window_width, window_height = ted->window_height; float char_height = text_font_char_height(font); @@ -314,6 +314,8 @@ int main(int argc, char **argv) { die("Not enough memory available to run ted."); } + ted->settings = &ted->settings_by_language[0]; + // make sure signal handler has access to ted. error_signal_handler_ted = ted; @@ -392,7 +394,6 @@ int main(int argc, char **argv) { #endif - Settings *settings = &ted->settings; char config_err[sizeof ted->error] = {0}; PROFILE_TIME(misc_end) @@ -412,17 +413,25 @@ int main(int argc, char **argv) { die("ted's backup config file, %s, does not exist. Try reinstalling ted?", global_config_filename); } } - config_read(ted, global_config_filename); - config_read(ted, local_config_filename); + + // read global settings + config_read(ted, global_config_filename, 0); + config_read(ted, local_config_filename, 0); + if (ted->search_cwd) { + // read config in cwd + config_read(ted, TED_CFG, 0); + } + // copy global settings to language-specific settings + for (int l = 1; l < LANG_COUNT; ++l) + ted->settings_by_language[l] = ted->settings_by_language[0]; + // read language-specific settings + config_read(ted, global_config_filename, 1); + config_read(ted, local_config_filename, 1); + if (ted->search_cwd) config_read(ted, TED_CFG, 1); if (ted_haserr(ted)) { strcpy(config_err, ted->error); ted_clearerr(ted); // clear the error so later things (e.g. loading font) don't detect an error } - - if (ted->search_cwd) { - // read config in cwd - config_read(ted, TED_CFG); - } } PROFILE_TIME(configs_end) @@ -525,7 +534,7 @@ int main(int argc, char **argv) { } - u32 *colors = settings->colors; (void)colors; + u32 *colors = ted->settings->colors; (void)colors; ted->cursor_ibeam = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); ted->cursor_arrow = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); @@ -729,7 +738,7 @@ int main(int argc, char **argv) { { TextBuffer *active_buffer = ted->active_buffer; if (active_buffer && buffer_externally_changed(active_buffer)) { - if (settings->auto_reload) + if (buffer_settings(active_buffer)->auto_reload) buffer_reload(active_buffer); else { strbuf_cpy(ted->ask_reload, buffer_get_filename(active_buffer)); @@ -774,7 +783,7 @@ int main(int argc, char **argv) { glViewport(0, 0, (GLsizei)window_width, (GLsizei)window_height); { // clear (background) float bg_color[4]; - rgba_u32_to_floats(settings->colors[COLOR_BG], bg_color); + rgba_u32_to_floats(colors[COLOR_BG], bg_color); glClearColor(bg_color[0], bg_color[1], bg_color[2], bg_color[3]); } glClear(GL_COLOR_BUFFER_BIT); @@ -786,7 +795,7 @@ int main(int argc, char **argv) { { - float const padding = settings->padding; + float const padding = ted->settings->padding; float x1 = padding, y = window_height-padding, x2 = window_width-padding; Node *node = &ted->nodes[0]; if (ted->find) { @@ -882,15 +891,15 @@ int main(int argc, char **argv) { if (*ted->error_shown) { double t = time_get_seconds(); double time_passed = t - ted->error_time; - if (time_passed > settings->error_display_time) { + if (time_passed > ted->settings->error_display_time) { // stop showing error *ted->error_shown = '\0'; } else { Rect r = error_box_rect(ted); - float padding = settings->padding; + float padding = ted->settings->padding; gl_geometry_rect(r, colors[COLOR_ERROR_BG]); - gl_geometry_rect_border(r, settings->border_thickness, colors[COLOR_ERROR_BORDER]); + gl_geometry_rect_border(r, ted->settings->border_thickness, colors[COLOR_ERROR_BORDER]); float text_x1 = rect_x1(r) + padding, text_x2 = rect_x2(r) - padding; float text_y1 = rect_y1(r) + padding; diff --git a/menu.c b/menu.c index 9e7a382..ae69eac 100644 --- a/menu.c +++ b/menu.c @@ -101,13 +101,13 @@ static void menu_escape(Ted *ted) { } static float menu_get_width(Ted *ted) { - Settings *settings = &ted->settings; + Settings const *settings = ted->settings; return minf(settings->max_menu_width, ted->window_width - 2.0f * settings->padding); } // returns the rectangle of the screen coordinates of the menu static Rect menu_rect(Ted *ted) { - Settings *settings = &ted->settings; + Settings *settings = ted->settings; float window_width = ted->window_width, window_height = ted->window_height; float padding = settings->padding; float menu_width = menu_get_width(ted); @@ -119,7 +119,7 @@ static Rect menu_rect(Ted *ted) { static void menu_update(Ted *ted) { Menu menu = ted->menu; - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; u32 const *colors = settings->colors; TextBuffer *line_buffer = &ted->line_buffer; @@ -316,7 +316,7 @@ static void menu_update(Ted *ted) { static void menu_render(Ted *ted) { Menu menu = ted->menu; assert(menu); - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; u32 const *colors = settings->colors; float const window_width = ted->window_width, window_height = ted->window_height; Font *font_bold = ted->font_bold, *font = ted->font; diff --git a/node.c b/node.c index 7898c3c..47cfb19 100644 --- a/node.c +++ b/node.c @@ -187,7 +187,7 @@ static bool node_tab_close(Ted *ted, Node *node, u16 index) { } static void node_frame(Ted *ted, Node *node, Rect r) { - Settings const *settings = &ted->settings; + Settings const *settings = ted_active_settings(ted); if (node->tabs) { bool is_active = node == ted->active_node; u32 const *colors = settings->colors; diff --git a/session.c b/session.c index 8e2061d..b609310 100644 --- a/session.c +++ b/session.c @@ -168,7 +168,7 @@ static void session_read_file(Ted *ted, FILE *fp) { } static void session_write(Ted *ted) { - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; if (!settings->restore_session) return; // first we write to a prefixed file so in case something goes wrong we still have the old session. @@ -189,7 +189,7 @@ static void session_write(Ted *ted) { } static void session_read(Ted *ted) { - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; if (settings->restore_session) { char filename[TED_PATH_MAX]; strbuf_printf(filename, "%s/" SESSION_FILENAME, ted->local_data_dir); diff --git a/syntax.c b/syntax.c index 9b5c726..bef3ea9 100644 --- a/syntax.c +++ b/syntax.c @@ -22,6 +22,7 @@ char const *language_comment_start(Language l) { case LANG_CPP: case LANG_JAVASCRIPT: case LANG_JAVA: + case LANG_GO: return "// "; case LANG_CONFIG: case LANG_PYTHON: diff --git a/tags.c b/tags.c index 399e3b1..e997a3e 100644 --- a/tags.c +++ b/tags.c @@ -36,7 +36,7 @@ static bool is_source_file(char const *filename) { static void tags_generate_at_dir(Ted *ted, bool run_in_build_window, const char *dir, int depth) { - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; if (depth >= settings->tags_max_depth) { return; } @@ -199,7 +199,7 @@ bool tag_goto(Ted *ted, char const *tag) { bool already_regenerated_tags; already_regenerated_tags = false; top:; - Settings const *settings = &ted->settings; + Settings const *settings = ted_active_settings(ted); char const *tags_name = tags_filename(ted, true); if (!tags_name) return false; @@ -385,7 +385,7 @@ static void tag_selector_close(Ted *ted) { // returns tag selected (should be free'd), or NULL if none was. static char *tag_selector_update(Ted *ted) { Selector *sel = &ted->tag_selector; - u32 color = ted->settings.colors[COLOR_TEXT]; + u32 color = ted->settings->colors[COLOR_TEXT]; sel->enable_cursor = true; // create selector entries based on search term diff --git a/ted.c b/ted.c index e98edde..e861736 100644 --- a/ted.c +++ b/ted.c @@ -48,6 +48,10 @@ static void *ted_realloc(Ted *ted, void *p, size_t new_size) { return ret; } +Settings *ted_active_settings(Ted *ted) { + return ted->active_buffer ? buffer_settings(ted->active_buffer) : ted->settings; +} + static void ted_path_full(Ted *ted, char const *relpath, char *abspath, size_t abspath_size) { path_full(ted->cwd, relpath, abspath, abspath_size); } @@ -81,7 +85,7 @@ static Status ted_get_file(Ted const *ted, char const *name, char *out, size_t o static void ted_load_font(Ted *ted, char const *filename, Font **out) { char font_filename[TED_PATH_MAX]; if (ted_get_file(ted, filename, font_filename, sizeof font_filename)) { - Font *font = text_font_load(font_filename, ted->settings.text_size); + Font *font = text_font_load(font_filename, ted_active_settings(ted)->text_size); if (font) { // we don't properly handle variable-width fonts text_font_set_force_monospace(font, true); @@ -199,9 +203,8 @@ static i32 ted_new_node(Ted *ted) { // how tall is a line buffer? static float ted_line_buffer_height(Ted const *ted) { - Settings const *settings = &ted->settings; float const char_height = text_font_char_height(ted->font); - return char_height + 2 * settings->border_thickness; + return char_height + 2 * ted->settings->border_thickness; } // switch to this node diff --git a/ted.cfg b/ted.cfg index fa37904..ab7344b 100644 --- a/ted.cfg +++ b/ted.cfg @@ -28,7 +28,8 @@ build-default-command = make tags-filename = tags # restore previously opened files when ted is launched? restore-session = on -# search depth for files to generate tags for. if set to 0, tag generation/regeneration will do nothing +# search depth for files to generate tags for. +# if set to 0, tag generation/regeneration will do nothing tags-max-depth = 2 # regenerate tags if an identifier is not found (with Ctrl+click)? regenerate-tags-if-not-found = yes diff --git a/ted.h b/ted.h index 01ad115..b7ecf19 100644 --- a/ted.h +++ b/ted.h @@ -296,7 +296,8 @@ typedef struct Ted { // the old active buffer needs to be restored. that's what this stores. TextBuffer *prev_active_buffer; Node *active_node; - Settings settings; + Settings settings_by_language[LANG_COUNT]; + Settings *settings; // "default" settings (equal to &settings_per_language[0]) float window_width, window_height; u32 key_modifier; // which of shift, alt, ctrl are down right now. v2 mouse_pos; @@ -398,4 +399,6 @@ typedef struct Ted { void command_execute(Ted *ted, Command c, i64 argument); void ted_switch_to_buffer(Ted *ted, TextBuffer *buffer); +// the settings of the active buffer, or the default settings if there is no active buffer +Settings *ted_active_settings(Ted *ted); static TextBuffer *find_search_buffer(Ted *ted); diff --git a/ui.c b/ui.c index 55132a5..060012a 100644 --- a/ui.c +++ b/ui.c @@ -3,7 +3,7 @@ #endif static float selector_entries_start_y(Ted const *ted, Selector const *s) { - float padding = ted->settings.padding; + float padding = ted->settings->padding; return s->bounds.pos.y + ted_line_buffer_height(ted) + padding; // make room for line buffer @@ -24,7 +24,7 @@ static void selector_clamp_scroll(Ted const *ted, Selector *s) { static void selector_scroll_to_cursor(Ted const *ted, Selector *s) { u32 n_display_entries = selector_n_display_entries(ted, s); - float scrolloff = ted->settings.scrolloff; + float scrolloff = ted->settings->scrolloff; float min_scroll = (float)s->cursor - ((float)n_display_entries - scrolloff); float max_scroll = (float)s->cursor - scrolloff; s->scroll = clampf(s->scroll, min_scroll, max_scroll); @@ -111,7 +111,7 @@ static char *selector_update(Ted *ted, Selector *s) { // NOTE: also renders the line buffer static void selector_render(Ted *ted, Selector *s) { - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; u32 const *colors = settings->colors; Font *font = ted->font; @@ -483,7 +483,7 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { } static void file_selector_render(Ted *ted, FileSelector *fs) { - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; u32 const *colors = settings->colors; Rect bounds = fs->bounds; Font *font = ted->font; @@ -523,12 +523,12 @@ static void file_selector_render(Ted *ted, FileSelector *fs) { } static v2 button_get_size(Ted *ted, char const *text) { - float border_thickness = ted->settings.border_thickness; + float border_thickness = ted->settings->border_thickness; return v2_add_const(text_get_size_v2(ted->font, text), 2 * border_thickness); } static void button_render(Ted *ted, Rect button, char const *text, u32 color) { - u32 const *colors = ted->settings.colors; + u32 const *colors = ted->settings->colors; if (rect_contains_point(button, ted->mouse_pos)) { // highlight button when hovering over it @@ -536,7 +536,7 @@ static void button_render(Ted *ted, Rect button, char const *text, u32 color) { gl_geometry_rect(button, new_color); } - gl_geometry_rect_border(button, ted->settings.border_thickness, colors[COLOR_BORDER]); + gl_geometry_rect_border(button, ted->settings->border_thickness, colors[COLOR_BORDER]); gl_geometry_draw(); v2 pos = rect_center(button); @@ -604,7 +604,7 @@ static void popup_render(Ted *ted, u32 options, char const *title, char const *b Font *font = ted->font; Font *font_bold = ted->font_bold; Rect r, button_yes, button_no, button_cancel; - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; u32 const *colors = settings->colors; float const char_height_bold = text_font_char_height(font_bold); float const padding = settings->padding; @@ -653,7 +653,7 @@ static v2 checkbox_frame(Ted *ted, bool *value, char const *label, v2 pos) { Font *font = ted->font; float char_height = text_font_char_height(font); float checkbox_size = char_height; - Settings const *settings = &ted->settings; + Settings const *settings = ted->settings; u32 const *colors = settings->colors; float padding = settings->padding; float border_thickness = settings->border_thickness; -- cgit v1.2.3