summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autocomplete.c10
-rw-r--r--buffer.c13
-rw-r--r--build.c2
-rw-r--r--command.c2
-rw-r--r--config.c684
-rw-r--r--find.c4
-rw-r--r--main.c41
-rw-r--r--menu.c8
-rw-r--r--node.c2
-rw-r--r--session.c4
-rw-r--r--syntax.c1
-rw-r--r--tags.c6
-rw-r--r--ted.c9
-rw-r--r--ted.cfg3
-rw-r--r--ted.h5
-rw-r--r--ui.c18
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;