summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2023-10-18 12:58:59 -0400
committerpommicket <pommicket@gmail.com>2023-10-18 12:58:59 -0400
commit4d57539ffc2954e5c3b9c42da41511f69c4fcde9 (patch)
treedb691445e296448deb2aa55cef6acb04a256b2ee
parent29282f362d5f663ec3da17b135751f3b9b2bd0fd (diff)
start editorconfig support
-rw-r--r--config.c159
-rw-r--r--main.c1
-rw-r--r--ted-internal.h25
-rw-r--r--ted.c20
-rw-r--r--util.c13
-rw-r--r--util.h4
6 files changed, 190 insertions, 32 deletions
diff --git a/config.c b/config.c
index 56a4ac7..387c797 100644
--- a/config.c
+++ b/config.c
@@ -286,6 +286,7 @@ static void config_free(Config *cfg) {
settings_free(&cfg->settings);
pcre2_code_free_8(cfg->path);
free(cfg->path_regex);
+ rc_str_decref(&cfg->source);
memset(cfg, 0, sizeof *cfg);
}
@@ -339,13 +340,12 @@ static void config_err_unexpected_eof(ConfigReader *reader) {
config_err(reader, "Unexpected EOF (no newline at end of file?)");
}
-static char config_getc(ConfigReader *reader) {
+static char config_getc_no_err_on_eof(ConfigReader *reader) {
int c = getc(reader->fp);
if (c == 0) {
config_err(reader, "Null byte in config file");
}
if (c == EOF) {
- config_err_unexpected_eof(reader);
c = 0;
}
if (c == '\n') {
@@ -354,6 +354,13 @@ static char config_getc(ConfigReader *reader) {
return (char)c;
}
+static char config_getc(ConfigReader *reader) {
+ char c = config_getc_no_err_on_eof(reader);
+ if (!c)
+ config_err_unexpected_eof(reader);
+ return c;
+}
+
static void config_ungetc(ConfigReader *reader, char c) {
if (c == '\n')
reader->line_number -= 1;
@@ -915,7 +922,20 @@ void settings_finalize(Ted *ted, Settings *settings) {
settings->text_size = clamp_u16((u16)roundf((float)settings->text_size_no_dpi * ted_get_ui_scaling(ted)), TEXT_SIZE_MIN, TEXT_SIZE_MAX);
}
-static void config_read_file(Ted *ted, const char *cfg_path, const char ***include_stack) {
+static void config_compile_regex(Config *cfg, ConfigReader *reader) {
+ if (cfg->path_regex) {
+ // compile regex
+ int error_code = 0;
+ PCRE2_SIZE error_offset = 0;
+ cfg->path = pcre2_compile_8((const u8 *)cfg->path_regex, PCRE2_ZERO_TERMINATED, PCRE2_ANCHORED, &error_code, &error_offset, NULL);
+ if (!cfg->path) {
+ config_err(reader, "Bad regex (at offset %u): %s", (unsigned)error_offset, cfg->path_regex);
+ free(cfg->path_regex); cfg->path_regex = NULL;
+ }
+ }
+}
+
+static bool config_read_ted_cfg(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)) {
@@ -931,18 +951,17 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu
strbuf_cat(text, ", which");
strbuf_catf(text, " includes %s", cfg_path);
ted_error(ted, "%s", text);
- return;
+ return false;
}
}
arr_add(*include_stack, cfg_path);
FILE *fp = fopen(cfg_path, "rb");
if (!fp) {
- ted_error(ted, "Couldn't open config file %s: %s.", cfg_path, strerror(errno));
- return;
+ return false;
}
-
+ RcStr *cfg_path_rc = rc_str_new(cfg_path, -1);
ConfigReader reader_data = {
.ted = ted,
.filename = cfg_path,
@@ -955,12 +974,8 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu
Config *cfg = NULL;
while (true) {
- int ic = getc(reader->fp);
- if (ic == EOF)
- break;
- char c = (char)ic;
- if (c == '\n') ++reader->line_number;
-
+ char c = config_getc_no_err_on_eof(reader);
+ if (!c) break;
if (c == '[') {
// a new section!
#define SECTION_HEADER_HELP "Section headers should look like this: [(path//)(language.)section-name]"
@@ -1036,16 +1051,9 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu
cfg = arr_addp(ted->all_configs);
cfg->path_regex = *path ? str_dup(path) : NULL;
cfg->language = language;
- if (cfg->path_regex) {
- // compile regex
- int error_code = 0;
- PCRE2_SIZE error_offset = 0;
- cfg->path = pcre2_compile_8((const u8 *)cfg->path_regex, PCRE2_ZERO_TERMINATED, PCRE2_ANCHORED, &error_code, &error_offset, NULL);
- if (!cfg->path) {
- config_err(reader, "Bad regex (at offset %u): %s", (unsigned)error_offset, cfg->path_regex);
- free(cfg->path_regex); cfg->path_regex = NULL;
- }
- }
+ cfg->format = CONFIG_TED_CFG;
+ cfg->source = rc_str_copy(cfg_path_rc);
+ config_compile_regex(cfg, reader);
}
} else if (c == '%') {
char line[2048];
@@ -1056,7 +1064,7 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu
strbuf_cpy(included, line + strlen("include "));
str_trim(included);
get_config_path(ted, expanded, sizeof expanded, included);
- config_read_file(ted, expanded, include_stack);
+ config_read_ted_cfg(ted, expanded, include_stack);
} else {
config_err(reader, "Unrecognized directive: %s", line);
}
@@ -1073,11 +1081,90 @@ static void config_read_file(Ted *ted, const char *cfg_path, const char ***inclu
config_err(reader, "Config has text before first section header.");
}
}
-
+ rc_str_decref(&cfg_path_rc);
if (ferror(fp))
ted_error(ted, "Error reading %s.", cfg_path);
fclose(fp);
arr_remove_last(*include_stack);
+ return true;
+}
+
+static bool config_read_editorconfig(Ted *ted, const char *path) {
+ FILE *fp = fopen(path, "r");
+ if (!fp) return false;
+ ConfigReader reader_data = {
+ .ted = ted,
+ .filename = path,
+ .line_number = 1,
+ .fp = fp,
+ };
+ ConfigReader *const reader = &reader_data;
+ Config *cfg = NULL;
+ char line[4096];
+ bool is_root = false;
+ RcStr *path_rc = rc_str_new(path, -1);
+ while (true) {
+ char c = config_getc_no_err_on_eof(reader);
+ if (!c) break;
+ if (isspace(c)) continue;
+ switch (c) {
+ case '#':
+ case ';':
+ config_read_to_eol(reader, line, sizeof line);
+ break;
+ case '[':
+ // new section
+ config_read_to_eol(reader, line, sizeof line);
+ str_trim(line);
+ if (strlen(line) == 0 || line[strlen(line) - 1] != ']') {
+ config_err(reader, "Unmatched [");
+ break;
+ }
+ cfg = arr_addp(ted->all_configs);
+ cfg->source = rc_str_copy(path_rc);
+ cfg->is_editorconfig_root = is_root;
+ cfg->format = CONFIG_EDITORCONFIG;
+ cfg->path_regex = str_dup("TODO"); // TODO
+ config_compile_regex(cfg, reader);
+ break;
+ default: {
+ char key[64] = {c};
+ for (size_t i = 1; i < sizeof key - 1; i++) {
+ c = config_getc(reader);
+ if (!c || c == '\n') break;
+ if (c == '=') break;
+ key[i] = c;
+ }
+ if (c != '=') {
+ config_err(reader, "expected key = value but didn't find =");
+ break;
+ }
+ str_trim(key);
+ str_ascii_to_lowercase(key);
+ char value[64] = {0};
+ for (size_t i = 0; i < sizeof value - 1; i++) {
+ c = config_getc(reader);
+ if (!c || c == '\n') break;
+ value[i] = c;
+ }
+ str_trim(value);
+ if (streq(key, "root")) {
+ str_ascii_to_lowercase(value);
+ if (cfg) {
+ config_err(reader, "root cannot be set outside of preamble");
+ }
+ if (streq(value, "true"))
+ is_root = true;
+ else
+ is_root = false;
+ }
+ }
+ break;
+ }
+ }
+ rc_str_decref(&path_rc);
+ fclose(fp);
+ return true;
}
void config_free_all(Ted *ted) {
@@ -1153,11 +1240,27 @@ char *settings_get_root_dir(const Settings *settings, const char *path) {
}
}
-void config_read(Ted *ted, const char *filename) {
+bool config_read(Ted *ted, const char *path, ConfigFormat format) {
const char **include_stack = NULL;
config_init_settings();
- config_read_file(ted, filename, &include_stack);
- ted_compute_settings(ted, "", LANG_NONE, &ted->default_settings);
+ // check if we've already read this
+ arr_foreach_ptr(ted->all_configs, Config, c) {
+ if (streq(rc_str(c->source, ""), path))
+ return true;
+ }
+ switch (format) {
+ case CONFIG_TED_CFG:
+ if (config_read_ted_cfg(ted, path, &include_stack)) {
+ // recompute default settings
+ ted_compute_settings(ted, "", LANG_NONE, &ted->default_settings);
+ return true;
+ }
+ return false;
+ case CONFIG_EDITORCONFIG:
+ return config_read_editorconfig(ted, path);
+ }
+ assert(0);
+ return false;
}
u32 settings_color(const Settings *settings, ColorSetting color) {
diff --git a/main.c b/main.c
index 81bd118..2658ca0 100644
--- a/main.c
+++ b/main.c
@@ -2,6 +2,7 @@
TODO:
- .editorconfig (see https://editorconfig.org/)
FUTURE FEATURES:
+- prepare rename support
- autodetect indentation (tabs vs spaces)
- custom file/build command associations
- config variables
diff --git a/ted-internal.h b/ted-internal.h
index af5f2dd..f6649a2 100644
--- a/ted-internal.h
+++ b/ted-internal.h
@@ -153,11 +153,29 @@ struct Settings {
KeyAction *key_actions;
};
+typedef enum {
+ CONFIG_TED_CFG = 1,
+ CONFIG_EDITORCONFIG = 2,
+} ConfigFormat;
+
typedef struct {
+ /// path to config file
+ RcStr *source;
+ /// format of config file
+ ConfigFormat format;
+ /// is this from a root .editorconfig file?
+ ///
+ /// (if so, we don't want to apply editorconfigs in higher-up directories)
+ bool is_editorconfig_root;
+ /// language this config applies to
Language language;
+ /// path regex this config applies to
struct pcre2_real_code_8 *path;
+ /// path regex string
char *path_regex;
+ /// settings which this config specifies
Settings settings;
+ /// which bytes of settings are actually set
bool settings_set[sizeof (Settings)];
} Config;
@@ -479,7 +497,12 @@ void command_init(void);
void command_execute_ex(Ted *ted, Command c, const CommandArgument *argument, const CommandContext *context);
// === config.c ===
-void config_read(Ted *ted, const char *filename);
+/// read a config file.
+///
+/// returns true on success. if the file at `path` does not exist, this returns false but doesn't show an error message.
+///
+/// if the config with this path has already been read, this does nothing.
+bool config_read(Ted *ted, const char *path, ConfigFormat format);
void config_free_all(Ted *ted);
void config_merge_into(Settings *dest, const Config *src_cfg);
/// call this after all your calls to \ref config_merge_into
diff --git a/ted.c b/ted.c
index 85e1a0b..5693092 100644
--- a/ted.c
+++ b/ted.c
@@ -233,6 +233,20 @@ static int applicable_configs_cmp(void *context, const void *av, const void *bv)
void ted_compute_settings(Ted *ted, const char *path, Language language, Settings *settings) {
settings_free(settings);
+
+ if (path && *path) {
+ // check for .editorconfig
+ char editorconfig[2048];
+ for (size_t i = 0; path[i] && i < sizeof editorconfig - 16; i++) {
+ editorconfig[i] = path[i];
+ editorconfig[i+1] = 0;
+ if (strchr(ALL_PATH_SEPARATORS, path[i])) {
+ strbuf_cat(editorconfig, ".editorconfig");
+ config_read(ted, editorconfig, CONFIG_EDITORCONFIG);
+ }
+ }
+ }
+
u32 *applicable_configs = NULL;
for (u32 i = 0; i < arr_len(ted->all_configs); i++) {
Config *cfg = &ted->all_configs[i];
@@ -736,13 +750,13 @@ void ted_load_configs(Ted *ted) {
}
- config_read(ted, global_config_filename);
- config_read(ted, local_config_filename);
+ config_read(ted, global_config_filename, CONFIG_TED_CFG);
+ config_read(ted, local_config_filename, CONFIG_TED_CFG);
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, start_cwd_filename);
+ config_read(ted, start_cwd_filename, CONFIG_TED_CFG);
}
}
diff --git a/util.c b/util.c
index 1649f91..b86a4ca 100644
--- a/util.c
+++ b/util.c
@@ -38,6 +38,12 @@ void rc_str_incref(RcStr *str) {
str->ref_count += 1;
}
+
+RcStr *rc_str_copy(RcStr *str) {
+ rc_str_incref(str);
+ return str;
+}
+
void rc_str_decref(RcStr **pstr) {
RcStr *const str = *pstr;
if (!str) return;
@@ -300,6 +306,13 @@ void str_trim(char *str) {
str_trim_start(str);
}
+void str_ascii_to_lowercase(char *str) {
+ for (char *p = str; *p; p++) {
+ if (*p > 0 && *p < 127)
+ *p = (char)tolower(*p);
+ }
+}
+
size_t str_count_char(const char *s, char c) {
const char *p = s;
size_t count = 0;
diff --git a/util.h b/util.h
index 1d60cf9..e190cd6 100644
--- a/util.h
+++ b/util.h
@@ -53,6 +53,8 @@ RcStr *rc_str_new(const char *s, i64 len);
///
/// does nothing if `str` is `NULL`.
void rc_str_incref(RcStr *str);
+/// increases reference count of `str` (if non-NULL) and returns it.
+RcStr *rc_str_copy(RcStr *str);
/// decrease reference count of `*str` and set `*str` to `NULL`.
///
/// this frees `*str` if the reference count hits 0.
@@ -122,6 +124,8 @@ void str_trim_start(char *str);
void str_trim_end(char *str);
/// trim whitespace from both sides of a string
void str_trim(char *str);
+/// convert ASCII to lowercase
+void str_ascii_to_lowercase(char *str);
/// count occurences of `c` in `s`
size_t str_count_char(const char *s, char c);
/// equivalent to GNU function asprintf (like sprintf, but allocates the string with malloc).