From b376a87775d10dc7a693c0e1ecbe59e867e4634a Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Mon, 1 Feb 2021 15:19:47 -0500 Subject: different syntax highlighting depending on the file extension --- buffer.c | 32 ++++++++++++++++++++++++++++++-- config.c | 31 ++++++++++++++++++++++++++++++- main.c | 1 + syntax.c | 11 ++++++++++- ted.cfg | 3 +++ ted.h | 15 +++++++++++++-- 6 files changed, 87 insertions(+), 6 deletions(-) diff --git a/buffer.c b/buffer.c index 2b9957c..fa16c20 100644 --- a/buffer.c +++ b/buffer.c @@ -177,6 +177,33 @@ static inline Settings const *buffer_settings(TextBuffer *buffer) { return &buffer->ted->settings; } +// what programming language is this? +Language buffer_language(TextBuffer *buffer) { + Settings const *settings = buffer_settings(buffer); + char const *filename = buffer->filename; + if (!filename) + return LANG_NONE; + size_t filename_len = strlen(filename); + + for (u16 l = 0; l < LANG_COUNT; ++l) { + char const *extensions = settings->language_extensions[l]; + if (extensions) { + // extensions is a string with commas separating each extension. + size_t len = 0; + for (char const *p = extensions; *p; p += len) { + if (*p == ',') ++p; // move past comma + len = strcspn(p, ","); + if (filename_len >= len && strncmp(&filename[filename_len - len], p, len) == 0) { + // found a match! + return (Language)l; + } + } + } + } + // no extensions matched + return LANG_NONE; +} + // 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) { @@ -1976,11 +2003,12 @@ void buffer_render(TextBuffer *buffer, Rect r) { text_state.y -= (float)(buffer->scroll_y - start_line) * char_height; SyntaxState syntax_state = {0}; + Language language = buffer_language(buffer); // dynamic array of character types, to be filled by syntax_highlight SyntaxCharType *char_types = NULL; for (u32 line_idx = 0; line_idx < start_line; ++line_idx) { Line *line = &lines[line_idx]; - syntax_highlight(&syntax_state, buffer->language, line->str, line->len, NULL); + syntax_highlight(&syntax_state, language, line->str, line->len, NULL); } for (u32 line_idx = start_line; line_idx < nlines; ++line_idx) { @@ -1988,7 +2016,7 @@ void buffer_render(TextBuffer *buffer, Rect r) { if (arr_len(char_types) < line->len) { arr_set_len(char_types, line->len); } - syntax_highlight(&syntax_state, buffer->language, line->str, line->len, char_types); + syntax_highlight(&syntax_state, language, line->str, line->len, char_types); for (u32 i = 0; i < line->len; ++i) { char32_t c = line->str[i]; SyntaxCharType type = char_types[i]; diff --git a/config.c b/config.c index 2ebc094..bf3b284 100644 --- a/config.c +++ b/config.c @@ -11,7 +11,8 @@ typedef enum { SECTION_NONE, SECTION_CORE, SECTION_KEYBOARD, - SECTION_COLORS + SECTION_COLORS, + SECTION_EXTENSIONS } Section; // all worth it for the -Wformat warnings @@ -227,6 +228,8 @@ void config_read(Ted *ted, char const *filename) { 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); } @@ -294,6 +297,25 @@ void config_read(Ted *ted, char const *filename) { 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; + settings->language_extensions[lang] = new_str; + } + } + } break; case SECTION_CORE: { char const *endptr; long long const integer = strtoll(value, (char **)&endptr, 10); @@ -380,3 +402,10 @@ void config_read(Ted *ted, char const *filename) { ted_seterr(ted, "Couldn't open file %s.", filename); } } + +static void settings_free(Settings *settings) { + for (u16 i = 0; i < LANG_COUNT; ++i) { + free(settings->language_extensions[i]); + settings->language_extensions[i] = NULL; + } +} diff --git a/main.c b/main.c index 0932609..bcad077 100644 --- a/main.c +++ b/main.c @@ -591,6 +591,7 @@ int main(int argc, char **argv) { buffer_free(&ted->line_buffer); text_font_free(ted->font); text_font_free(ted->font_bold); + settings_free(&ted->settings); free(ted); #if _WIN32 for (int i = 0; i < argc; ++i) diff --git a/syntax.c b/syntax.c index eff776c..a4378b1 100644 --- a/syntax.c +++ b/syntax.c @@ -1,3 +1,12 @@ +// returns the language this string is referring to, or LANG_NONE if it's invalid. +Language language_from_str(char const *str) { + for (int i = 0; i < LANG_COUNT; ++i) { + if (streq(language_names[i].name, str)) + return language_names[i].lang; + } + return LANG_NONE; +} + // NOTE: returns the color setting, not the color ColorSetting syntax_char_type_to_color(SyntaxCharType t) { switch (t) { @@ -210,7 +219,7 @@ static void syntax_highlight_c(SyntaxStateC *state, char32_t *line, u32 line_len // You can set char_types to NULL if you just want to advance the state, and don't care about the character types. void syntax_highlight(SyntaxState *state, Language lang, char32_t *line, u32 line_len, SyntaxCharType *char_types) { switch (lang) { - case LANG_TEXT: + case LANG_NONE: memset(char_types, 0, line_len * sizeof *char_types); break; case LANG_C: diff --git a/ted.cfg b/ted.cfg index 7e2877f..f76c6bd 100644 --- a/ted.cfg +++ b/ted.cfg @@ -137,3 +137,6 @@ string = #f77 character = #a7f comment = #999 constant = #8ff + +[extensions] +C = .c, .h diff --git a/ted.h b/ted.h index 2fcc541..59247fb 100644 --- a/ted.h +++ b/ted.h @@ -16,11 +16,21 @@ typedef union { } SyntaxState; ENUM_U16 { - LANG_TEXT, + LANG_NONE, LANG_C, LANG_COUNT } ENUM_U16_END(Language); +typedef struct { + Language lang; + char const *name; +} LanguageName; + +static LanguageName const language_names[] = { + {LANG_NONE, "None"}, + {LANG_C, "C"}, +}; + ENUM_U8 { SYNTAX_NORMAL, SYNTAX_KEYWORD, @@ -45,6 +55,8 @@ typedef struct { u8 border_thickness; u8 padding; u8 scrolloff; + // [i] = comma-separated string of file extensions for language i, or NULL for none + char *language_extensions[LANG_COUNT]; } Settings; #define SCANCODE_COUNT 0x120 // SDL scancodes should be less than this value. @@ -91,7 +103,6 @@ typedef struct { double scroll_x, scroll_y; // number of characters scrolled in the x/y direction BufferPos cursor_pos; BufferPos selection_pos; // if selection is true, the text between selection_pos and cursor_pos is selected. - Language language; bool is_line_buffer; // "line buffers" are buffers which can only have one line of text (used for inputs) bool selection; bool store_undo_events; // set to false to disable undo events -- cgit v1.2.3