diff options
-rw-r--r-- | buffer.c | 26 | ||||
-rw-r--r-- | config.c | 117 | ||||
-rw-r--r-- | main.c | 4 | ||||
-rw-r--r-- | ted-internal.h | 27 | ||||
-rw-r--r-- | ted.c | 86 | ||||
-rw-r--r-- | ted.h | 16 |
6 files changed, 167 insertions, 109 deletions
@@ -85,6 +85,8 @@ struct TextBuffer { /// capacity of \ref lines u32 lines_capacity; + /// if false, need to recompute settings. + bool settings_computed; Settings settings; /// which LSP this document is open in LSPID lsp_opened_in; @@ -352,7 +354,6 @@ static char *buffer_strdup(TextBuffer *buffer, const char *src) { static void buffer_set_up(Ted *ted, TextBuffer *buffer) { buffer->store_undo_events = true; buffer->ted = ted; - buffer->settings_idx = -1; } static void line_buffer_set_up(Ted *ted, TextBuffer *buffer) { @@ -454,7 +455,7 @@ Language buffer_language(TextBuffer *buffer) { if (buffer->manual_language != LANG_NONE) return (Language)buffer->manual_language; - const Settings *settings = buffer->ted->default_settings; // important we don't use buffer_settings here since that would cause infinite recursion! + const Settings *settings = ted_default_settings(buffer->ted); // important we don't use buffer_settings here since that would cause infinite recursion! const char *filename = path_filename(buffer->path); int match_score = 0; @@ -510,7 +511,7 @@ LSP *buffer_lsp(TextBuffer *buffer) { return ted_get_lsp_by_id(buffer->ted, buffer->lsp_opened_in); } - LSP *true_lsp = ted_get_lsp(buffer->ted, buffer->path, buffer_language(buffer)); + LSP *true_lsp = ted_get_lsp(buffer->ted, buffer_settings(buffer), buffer->path); LSP *curr_lsp = ted_get_lsp_by_id(buffer->ted, buffer->lsp_opened_in); if (true_lsp != curr_lsp) { if (curr_lsp) @@ -526,13 +527,11 @@ LSP *buffer_lsp(TextBuffer *buffer) { Settings *buffer_settings(TextBuffer *buffer) { Ted *ted = buffer->ted; - if (buffer->settings_idx >= 0 && buffer->settings_idx < (i32)arr_len(ted->all_settings)) - return &ted->all_settings[buffer->settings_idx]; - - Settings *settings = ted_get_settings(ted, buffer->path, buffer_language(buffer)); - buffer->settings_idx = (i32)(settings - ted->all_settings); - assert(buffer->settings_idx >= 0 && buffer->settings_idx < (i32)arr_len(ted->all_settings)); - return settings; + if (!buffer->settings_computed) { + ted_compute_settings(ted, buffer->path, buffer_language(buffer), &buffer->settings); + buffer->settings_computed = true; + } + return &buffer->settings; } u8 buffer_tab_width(TextBuffer *buffer) { @@ -1001,6 +1000,7 @@ static void buffer_free_inner(TextBuffer *buffer) { buffer_diagnostics_clear(buffer); arr_free(buffer->undo_history); arr_free(buffer->redo_history); + settings_free(&buffer->settings); memset(buffer, 0, sizeof *buffer); } @@ -2980,7 +2980,7 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { long file_pos = ftell(fp); size_t file_size = (size_t)file_pos; fseek(fp, 0, SEEK_SET); - const Settings *default_settings = buffer->ted->default_settings; + const Settings *default_settings = ted_default_settings(buffer->ted); u32 max_file_size_editable = default_settings->max_file_size; u32 max_file_size_view_only = default_settings->max_file_size_view_only; @@ -3057,7 +3057,7 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { if (success) { // everything is good buffer_clear(buffer); - buffer->settings_idx = -1; + buffer->settings_computed = false; buffer->lines = lines; buffer->nlines = nlines; buffer->frame_earliest_line_modified = 0; @@ -3255,7 +3255,7 @@ bool buffer_save_as(TextBuffer *buffer, const char *new_path) { LSP *lsp = buffer_lsp(buffer); char *prev_path = buffer->path; buffer->path = buffer_strdup(buffer, new_path); - buffer->settings_idx = -1; // we might have new settings + buffer->settings_computed = false; // we might have new settings if (buffer->path && buffer_save(buffer)) { buffer->view_only = false; @@ -129,6 +129,7 @@ static const SettingU16 settings_u16[] = { {"framerate-cap", &settings_zero.framerate_cap, 3, 1000, false}, {"lsp-port", &settings_zero.lsp_port, 0, 65535, true}, }; +const SettingU16 setting_text_size_dpi_aware = {NULL, &settings_zero.text_size, 0, U16_MAX, false}; static const SettingU32 settings_u32[] = { {"max-file-size", &settings_zero.max_file_size, 100, 2000000000, false}, {"max-file-size-view-only", &settings_zero.max_file_size_view_only, 100, 2000000000, false}, @@ -157,6 +158,26 @@ static const SettingKeyCombo settings_key_combo[] = { }; +bool config_applies_to(Config *cfg, const char *path, Language language) { + if (cfg->language && language != cfg->language) + return false; + if (cfg->path && !str_has_path_prefix(path, cfg->path)) + return false; + return true; +} +static bool config_has_same_context(const Config *a, const Config *b) { + if (a->language != b->language) + return false; + if (a->path && !b->path) + return false; + if (!a->path && b->path) + return false; + if (a->path && !streq(a->path, b->path)) + return false; + return true; +} + + 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); @@ -191,7 +212,7 @@ static void config_set_string(Config *cfg, const SettingString *set, const char RcStr **control = (RcStr **)((char *)settings + offset); if (*control) rc_str_decref(control); RcStr *rc = rc_str_new(value, -1); - config_set_setting(cfg, offset, &rc, sizeof rc); + config_set_setting(cfg, offset, &rc, sizeof (RcStr *)); } 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); @@ -236,7 +257,7 @@ static void settings_copy(Settings *dest, const Settings *src) { dest->key_actions = arr_copy(src->key_actions); } -static void settings_free(Settings *settings) { +void settings_free(Settings *settings) { arr_free(settings->language_extensions); gl_rc_sab_decref(&settings->bg_shader); gl_rc_texture_decref(&settings->bg_texture); @@ -254,8 +275,13 @@ static void config_free(Config *cfg) { memset(cfg, 0, sizeof *cfg); } -static void config_merge(Config *dest_cfg, const Config *src_cfg) { - Settings *dest = &dest_cfg->settings; + +i32 config_priority(const Config *cfg) { + size_t path_len = cfg->path ? strlen(cfg->path) : 0; + return (i32)path_len * 2 + (cfg->language != 0); +} + +void config_merge_into(Settings *dest, const Config *src_cfg) { const Settings *src = &src_cfg->settings; char *destc = (char *)dest; const char *srcc = (const char *)src; @@ -263,6 +289,7 @@ static void config_merge(Config *dest_cfg, const Config *src_cfg) { LanguageExtension *const src_exts = src->language_extensions; KeyAction *const dest_keys = dest->key_actions; KeyAction *const src_keys = src->key_actions; + // TODO: decrement reference counts, free language_extensions if needed for (size_t i = 0; i < sizeof(Settings); i++) { if (src_cfg->settings_set[i]) destc[i] = srcc[i]; @@ -431,13 +458,16 @@ static void get_config_path(Ted *ted, char *expanded, size_t expanded_sz, const } -static char *config_read_string(Ted *ted, ConfigReader *reader, char **ptext) { - char *p; +// only reads fp for multi-line strings +static char *config_read_string(Ted *ted, ConfigReader *reader, const char *first_line, FILE *fp) { + const char *p; + char line_buf[1024]; u32 start_line = reader->line_number; - char delimiter = **ptext; - char *start = *ptext + 1; + char delimiter = *first_line; char *str = NULL; - for (p = start; *p != delimiter; ++p) { + bool increment_p = true; + for (p = first_line + 1; *p != delimiter; p += increment_p) { + increment_p = true; switch (*p) { case '\\': ++p; @@ -449,31 +479,38 @@ static char *config_read_string(Ted *ted, ConfigReader *reader, char **ptext) { case 'n': arr_add(str, '\n'); continue; + case 'r': + arr_add(str, '\r'); + continue; case 't': arr_add(str, '\t'); continue; case '[': arr_add(str, '['); continue; - case '\0': - goto null; default: config_err(reader, "Unrecognized escape sequence: '\\%c'.", *p); - *ptext += strlen(*ptext); arr_clear(str); return NULL; } break; - case '\n': + case '\0': ++reader->line_number; + arr_add(str, '\n'); + if (!fgets(line_buf, sizeof line_buf, fp)) { + reader->line_number = start_line; + config_err(reader, "String doesn't end."); + arr_clear(str); + return NULL; + } + line_buf[strcspn(line_buf, "\r\n")] = 0; + p = line_buf; + increment_p = false; + continue; + case '\r': + case '\n': + assert(false); break; - case '\0': - null: - reader->line_number = start_line; - config_err(reader, "String doesn't end."); - *ptext += strlen(*ptext); - arr_clear(str); - return NULL; } arr_add(str, *p); } @@ -484,7 +521,6 @@ static char *config_read_string(Ted *ted, ConfigReader *reader, char **ptext) { ted->strings[ted->nstrings++] = s; } arr_clear(str); - *ptext = p + 1; return s; } @@ -796,6 +832,9 @@ static void config_parse_line(ConfigReader *reader, Config *cfg, char *line, FIL } } break; } + if (streq(setting_any->name, "text-size")) { + config_set_u16(cfg, &setting_text_size_dpi_aware, (u16)roundf((float)integer * ted_get_ui_scaling(ted))); + } } break; } @@ -865,12 +904,13 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu #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, ']'); + Language language = 0; if (!closing) { config_err(reader, "Unmatched [. " SECTION_HEADER_HELP); - return false; + break; } else if (closing[1] != '\0') { config_err(reader, "Text after section. " SECTION_HEADER_HELP); - return false; + break; } else { *closing = '\0'; char *section = line + 1; @@ -891,7 +931,6 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu if (*p == '/') *p = '\\'; #endif - cfg->path = str_dup(path); section = path_end + 2; } @@ -899,7 +938,7 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu if (dot) { *dot = '\0'; - Language language = cfg->language = language_from_str(section); + language = language_from_str(section); if (!language) { config_err(reader, "Unrecognized language: %s.", section); } @@ -913,15 +952,30 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu } else if (streq(section, "core")) { reader->section = SECTION_CORE; } else if (streq(section, "extensions")) { - if (cfg->language != 0 || cfg->path) { + if (language != 0 || *path) { config_err(reader, "Extensions section cannot be language- or path-specific."); - return; + break; } reader->section = SECTION_EXTENSIONS; } else { config_err(reader, "Unrecognized section: [%s].", section); } } + Config new_cfg = { + .language = language, + .path = *path ? path : NULL, + }; + cfg = NULL; + // search for config with same context to update + arr_foreach_ptr(ted->all_configs, Config, c) { + if (config_has_same_context(c, &new_cfg)) { + cfg = c; + } + } + if (!cfg) { + // create new config + cfg = arr_addp(ted->all_configs); + } } else if (line[0] == '%') { if (str_has_prefix(line, "%include ")) { char included[TED_PATH_MAX]; @@ -962,7 +1016,7 @@ void config_free_all(Ted *ted) { ted->strings[i] = NULL; } ted->nstrings = 0; - ted->default_settings = NULL; + settings_free(&ted->default_settings); } @@ -1030,6 +1084,12 @@ char *settings_get_root_dir(const Settings *settings, const char *path) { } } +void config_read(Ted *ted, const char *filename) { + const char **include_stack = NULL; + config_read_file(ted, filename, &include_stack); + ted_compute_settings(ted, "", LANG_NONE, &ted->default_settings); +} + u32 settings_color(const Settings *settings, ColorSetting color) { if (color >= COLOR_COUNT) { assert(0); @@ -1062,3 +1122,4 @@ float settings_border_thickness(const Settings *settings) { float settings_padding(const Settings *settings) { return settings->padding; } + @@ -1,7 +1,7 @@ /* TODO: +- get rid of ted->strings; do cfg->strings instead. - 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) @@ -1166,7 +1166,7 @@ int main(int argc, char **argv) { { // annoyingly, SDL_GL_SwapWindow seems to be a busy loop on my laptop for some reason... // this is why the framerate-cap settings exists - const Settings *settings = ted->default_settings; + const Settings *settings = ted_default_settings(ted); if (settings->framerate_cap) { i32 ms_wait = 1000 / (i32)settings->framerate_cap - (i32)((frame_end_noswap - frame_start) * 1000); ms_wait -= 1; // give swap an extra ms to make sure it's actually vsynced diff --git a/ted-internal.h b/ted-internal.h index 09ba7c9..ad7ce3a 100644 --- a/ted-internal.h +++ b/ted-internal.h @@ -55,16 +55,6 @@ typedef struct { CommandArgument argument; } KeyAction; - -/// A SettingsContext is a context where a specific set of settings are applied. -/// this corresponds to `[PATH//LANGUAGE.(section)]` in config files. -typedef struct { - /// The settings apply to this language. - Language language; - /// The settings apply to all paths which start with this string, or all paths if path=NULL - char *path; -} SettingsContext; - /// Need to use reference counting for textures because of Settings: /// We copy parent settings to children /// e.g. @@ -287,8 +277,10 @@ struct Ted { TextBuffer *prev_active_buffer; Node *active_node; Config *all_configs; + /// cwd where \ref default_settings was computed + char default_settings_cwd[TED_PATH_MAX]; /// settings to use when no buffer is open - Settings *default_settings; + Settings default_settings; float window_width, window_height; vec2 mouse_pos; u32 mouse_state; @@ -501,9 +493,11 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *argument, co // === config.c === 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); +void config_merge_into(Settings *dest, const Config *src_cfg); +bool config_applies_to(Config *cfg, const char *path, Language language); +/// higher-priority configs override lower-priority ones. +i32 config_priority(const Config *cfg); +void settings_free(Settings *settings); // === find.c === void find_init(Ted *ted); @@ -702,6 +696,7 @@ void ted_update_time(Ted *ted); void ted_reset_active_buffer(Ted *ted); /// set ted's error message to the buffer's error. void ted_error_from_buffer(Ted *ted, TextBuffer *buffer); +float ted_get_ui_scaling(Ted *ted); /// Get LSP by ID. Returns NULL if there is no LSP with that ID. LSP *ted_get_lsp_by_id(Ted *ted, LSPID id); /// go to this LSP document position, opening a new buffer containing the file if necessary. @@ -715,6 +710,10 @@ MessageType ted_message_type_from_lsp(LSPWindowMessageType type); void ted_delete_buffer(Ted *ted, TextBuffer *buffer); /// Returns a new buffer, or NULL on out of memory TextBuffer *ted_new_buffer(Ted *ted); +/// Compute the settings for a file at the given path in the given language. +/// +/// NOTE: this frees the previous settings stored in `*settings`. so make sure it's either zeroed or points to valid settings. +void ted_compute_settings(Ted *ted, const char *path, Language language, Settings *settings); /// check for orphaned nodes and node cycles void ted_check_for_node_problems(Ted *ted); /// load ted configuration @@ -212,37 +212,50 @@ char *ted_get_root_dir(Ted *ted) { } } -Settings *ted_active_settings(Ted *ted) { - if (ted->active_buffer) - return buffer_settings(ted->active_buffer); - Settings *settings = ted->default_settings; - int settings_score = 0; - arr_foreach_ptr(ted->all_settings, Settings, s) { - const SettingsContext *c = &s->context; - if (c->language != 0) continue; - if (!c->path || !str_has_prefix(ted->cwd, c->path)) continue; - int score = (int)strlen(c->path); - if (score > settings_score) { - settings = s; - settings_score = score; +static int applicable_configs_cmp(void *context, const void *av, const void *bv) { + const Config *const all_configs = context; + const u32 *ai = av, *bi = bv; + const Config *ac = &all_configs[*ai], *bc = &all_configs[*bi]; + const i32 a = config_priority(ac); + const i32 b = config_priority(bc); + if (a < b) return -1; + if (a > b) return 1; + return 0; +} + +void ted_compute_settings(Ted *ted, const char *path, Language language, Settings *settings) { + settings_free(settings); + memset(settings, 0, sizeof *settings); + u32 *applicable_configs = NULL; + for (u32 i = 0; i < arr_len(ted->all_configs); i++) { + Config *cfg = &ted->all_configs[i]; + if (config_applies_to(cfg, path, language)) { + arr_add(applicable_configs, i); } } - return settings; + qsort_with_context(applicable_configs, arr_len(applicable_configs), + sizeof applicable_configs[0], applicable_configs_cmp, ted->all_configs); + arr_foreach_ptr(applicable_configs, const u32, i) { + config_merge_into(settings, &ted->all_configs[*i]); + } + arr_free(applicable_configs); } -Settings *ted_get_settings(Ted *ted, const char *path, Language language) { - long best_score = 0; - Settings *settings = ted->default_settings; - arr_foreach_ptr(ted->all_settings, Settings, s) { - long score = context_score(path, language, &s->context); - if (score > best_score) { - best_score = score; - settings = s; - } +Settings *ted_default_settings(Ted *ted) { + if (!streq(ted->default_settings_cwd, ted->cwd)) { + // recompute default settings + ted_compute_settings(ted, ted->cwd, LANG_NONE, &ted->default_settings); } - return settings; + return &ted->default_settings; } +Settings *ted_active_settings(Ted *ted) { + if (ted->active_buffer) + return buffer_settings(ted->active_buffer); + return ted_default_settings(ted); +} + + LSP *ted_get_lsp_by_id(Ted *ted, LSPID id) { if (id == 0) return NULL; for (int i = 0; ted->lsps[i]; ++i) { @@ -253,8 +266,7 @@ LSP *ted_get_lsp_by_id(Ted *ted, LSPID id) { return NULL; } -LSP *ted_get_lsp(Ted *ted, const char *path, Language language) { - const Settings *settings = ted_get_settings(ted, path, language); +LSP *ted_get_lsp(Ted *ted, Settings *settings, const char *path) { if (!settings->lsp_enabled) return NULL; @@ -405,7 +417,7 @@ static Font *ted_load_multifont(Ted *ted, const char *filenames) { return first_font; } -static float ted_get_ui_scaling(Ted *ted) { +float ted_get_ui_scaling(Ted *ted) { #if _WIN32 SDL_SysWMinfo wm_info; SDL_VERSION(&wm_info.version); @@ -423,15 +435,6 @@ static float ted_get_ui_scaling(Ted *ted) { } void ted_load_fonts(Ted *ted) { - { - // compute text size - float scaling = ted_get_ui_scaling(ted); - arr_foreach_ptr(ted->all_settings, Settings, set) { - u16 size = (u16)roundf(scaling * (float)set->text_size_no_dpi); - set->text_size = clamp_u16(size, TEXT_SIZE_MIN, TEXT_SIZE_MAX); - } - } - ted_free_fonts(ted); const Settings *settings = ted_active_settings(ted); ted->font = ted_load_multifont(ted, rc_str(settings->font, "")); @@ -724,21 +727,18 @@ void ted_load_configs(Ted *ted) { } - ConfigPart *parts = NULL; - config_read(ted, &parts, global_config_filename); - config_read(ted, &parts, local_config_filename); + config_read(ted, global_config_filename); + config_read(ted, local_config_filename); if (ted->search_start_cwd) { // read config in start_cwd char start_cwd_filename[TED_PATH_MAX]; strbuf_printf(start_cwd_filename, "%s%c" TED_CFG, ted->start_cwd, PATH_SEPARATOR); - - config_read(ted, &parts, start_cwd_filename); + config_read(ted, start_cwd_filename); } - config_parse(ted, &parts); } void ted_reload_configs(Ted *ted) { - config_free(ted); + config_free_all(ted); ted_load_configs(ted); // reset text size ted_load_fonts(ted); @@ -1182,17 +1182,15 @@ char *ted_get_root_dir_of(Ted *ted, const char *path); /// /// The returned value should be freed. char *ted_get_root_dir(Ted *ted); +/// settings to use when no buffer is open +Settings *ted_default_settings(Ted *ted); /// the settings of the active buffer, or the default settings if there is no active buffer Settings *ted_active_settings(Ted *ted); -/// Get the settings for a file at the given path in the given language. -Settings *ted_get_settings(Ted *ted, const char *path, Language language); -/// Get LSP server which should be used for the given path and language. -/// -/// If no running server would cover the path and language, a new one is -/// started if possible. -/// Returns `NULL` on failure (e.g. there is no LSP server -/// specified for the given path and language). -struct LSP *ted_get_lsp(Ted *ted, const char *path, Language language); +/// Get LSP server which should be used for the given settings and path. +/// +/// The server is started if necessary. +/// Returns `NULL` on failure (e.g. there is no LSP server configured). +struct LSP *ted_get_lsp(Ted *ted, Settings *settings, const char *path); /// Get the LSP server of the active buffer or directory. /// /// Returns `NULL` if there is no such server. |