diff options
| author | Albert Cervin <albert@acervin.com> | 2023-03-17 08:43:41 +0100 |
|---|---|---|
| committer | Albert Cervin <albert@acervin.com> | 2023-03-17 08:43:41 +0100 |
| commit | 0e61e64b4e1036f2bf107efb01bea1017893d5e6 (patch) | |
| tree | afed627a8e4a0d3136e6f5ff3e6ca1a1a7274ca6 /src | |
| parent | 40db61eb7a2019ced97f09a9687139f35749f4e0 (diff) | |
| download | dged-0e61e64b4e1036f2bf107efb01bea1017893d5e6.tar.gz dged-0e61e64b4e1036f2bf107efb01bea1017893d5e6.tar.xz dged-0e61e64b4e1036f2bf107efb01bea1017893d5e6.zip | |
Implement support for languages
Uses the settings system to implement a small system for per-language
settings.
Diffstat (limited to 'src')
| -rw-r--r-- | src/buffer.c | 31 | ||||
| -rw-r--r-- | src/buffer.h | 4 | ||||
| -rw-r--r-- | src/lang.c | 160 | ||||
| -rw-r--r-- | src/lang.h | 46 | ||||
| -rw-r--r-- | src/main.c | 2 | ||||
| -rw-r--r-- | src/settings.c | 29 |
6 files changed, 256 insertions, 16 deletions
diff --git a/src/buffer.c b/src/buffer.c index e6c3552..f7bc0e3 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -2,6 +2,7 @@ #include "binding.h" #include "display.h" #include "errno.h" +#include "lang.h" #include "minibuffer.h" #include "reactor.h" #include "settings.h" @@ -62,6 +63,7 @@ struct buffer buffer_create(char *name, bool modeline) { .scroll = {0}, .update_hooks = {0}, .nkeymaps_max = 10, + .lang = lang_from_id("fnd"), }; undo_init(&b.undo, 100); @@ -154,6 +156,10 @@ void buffer_static_init() { settings_register_setting( "editor.tab-width", (struct setting_value){.type = Setting_Number, .number_value = 4}); + + settings_register_setting( + "editor.show-whitespace", + (struct setting_value){.type = Setting_Bool, .bool_value = true}); } void buffer_static_teardown() { @@ -502,6 +508,10 @@ struct buffer buffer_from_file(char *filename) { fclose(file); } + const char *ext = strrchr(b.filename, '.'); + if (ext != NULL) { + b.lang = lang_from_extension(ext + 1); + } undo_push_boundary(&b.undo, (struct undo_boundary){.save_point = true}); return b; } @@ -598,10 +608,9 @@ void buffer_newline(struct buffer *buffer) { } void buffer_indent(struct buffer *buffer) { - struct setting *setting = settings_get("editor.tab-width"); - buffer_add_text( - buffer, (uint8_t *)" ", - setting->value.number_value > 16 ? 16 : setting->value.number_value); + uint32_t tab_width = buffer->lang.tab_width; + buffer_add_text(buffer, (uint8_t *)" ", + tab_width > 16 ? 16 : tab_width); } uint32_t buffer_add_update_hook(struct buffer *buffer, update_hook_cb hook, @@ -701,6 +710,8 @@ struct cmdbuf { struct region region; bool mark_set; + bool show_ws; + struct line_render_hook *line_render_hooks; uint32_t nlinerender_hooks; }; @@ -722,7 +733,7 @@ void render_line(struct text_chunk *line, void *userdata) { uint32_t text_nchars = scroll_bytes > line->nchars ? 0 : line->nchars - cmdbuf->scroll.col; - command_list_set_show_whitespace(cmdbuf->cmds, true); + command_list_set_show_whitespace(cmdbuf->cmds, cmdbuf->show_ws); struct buffer_location *begin = &cmdbuf->region.begin, *end = &cmdbuf->region.end; @@ -864,9 +875,10 @@ struct update_hook_result buffer_modeline_hook(struct buffer *buffer, struct tm *lt = localtime(&now); char left[128], right[128]; - snprintf(left, 128, " %c%c %-16s (%d, %d)", buffer->modified ? '*' : '-', - buffer->readonly ? '%' : '-', buffer->name, buffer->dot.line + 1, - visual_dot_col(buffer, buffer->dot.col)); + snprintf(left, 128, " %c%c %-16s (%d, %d) (%s)", + buffer->modified ? '*' : '-', buffer->readonly ? '%' : '-', + buffer->name, buffer->dot.line + 1, + visual_dot_col(buffer, buffer->dot.col), buffer->lang.name); snprintf(right, 128, "(%.2f ms) %02d:%02d", frame_time / 1e6, lt->tm_hour, lt->tm_min); @@ -1003,6 +1015,8 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, scroll(buffer, line_delta, col_delta); + struct setting *show_ws = settings_get("editor.show-whitespace"); + struct cmdbuf cmdbuf = (struct cmdbuf){ .cmds = commands, .scroll = buffer->scroll, @@ -1013,6 +1027,7 @@ void buffer_update(struct buffer *buffer, uint32_t width, uint32_t height, .nlinerender_hooks = nlinehooks, .mark_set = buffer->mark_set, .region = to_region(buffer->dot, buffer->mark), + .show_ws = show_ws != NULL ? show_ws->value.bool_value : true, }; text_for_each_line(buffer->text, buffer->scroll.line, height, render_line, &cmdbuf); diff --git a/src/buffer.h b/src/buffer.h index 9a312c0..0eb6287 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -3,6 +3,7 @@ #include <stdio.h> #include "command.h" +#include "lang.h" #include "text.h" #include "undo.h" #include "window.h" @@ -136,6 +137,9 @@ struct buffer { /** Modeline buffer (may be NULL) */ struct modeline *modeline; + + /** Buffer programming language */ + struct language lang; }; struct buffer buffer_create(char *name, bool modeline); diff --git a/src/lang.c b/src/lang.c new file mode 100644 index 0000000..6919780 --- /dev/null +++ b/src/lang.c @@ -0,0 +1,160 @@ +#include "lang.h" +#include "minibuffer.h" +#include "settings.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +void define_lang(const char *name, const char *id, const char *extensions, + uint32_t tab_width, const char *lang_srv) { + char namebuf[128] = {0}; + + size_t offs = snprintf(namebuf, 128, "languages.%s.", id); + + char *b = namebuf + offs; + snprintf(b, 128 - offs, "%s", "extensions"); + settings_register_setting( + namebuf, (struct setting_value){.type = Setting_String, + .string_value = (char *)extensions}); + + snprintf(b, 128 - offs, "%s", "tab-width"); + settings_register_setting(namebuf, + (struct setting_value){.type = Setting_Number, + .number_value = tab_width}); + + snprintf(b, 128 - offs, "%s", "lang-srv"); + settings_register_setting( + namebuf, (struct setting_value){.type = Setting_String, + .string_value = (char *)lang_srv}); + + snprintf(b, 128 - offs, "%s", "name"); + settings_register_setting( + namebuf, (struct setting_value){.type = Setting_String, + .string_value = (char *)name}); +} + +static struct language g_fundamental = { + .name = "Fundamental", + .tab_width = 4, + .lang_srv = NULL, +}; + +void languages_init(bool register_default) { + if (register_default) { + define_lang("C", "c", "c h", 2, "clangd"); + define_lang("C++", "cxx", "cpp cxx cc c++ hh h", 2, "clangd"); + define_lang("Rust", "rs", "rs", 4, "rust-analyzer"); + define_lang("Nix", "nix", "nix", 4, "rnix-lsp"); + } +} + +struct language lang_from_settings(const char *lang_path) { + char setting_name_buf[128] = {0}; + size_t offs = snprintf(setting_name_buf, 128, "%s.", lang_path); + char *b = setting_name_buf + offs; + + struct language l; + + snprintf(b, 128 - offs, "%s", "name"); + struct setting *name = settings_get(setting_name_buf); + l.name = name != NULL ? name->value.string_value : "Unknown"; + + snprintf(b, 128 - offs, "%s", "tab-width"); + struct setting *tab_width = settings_get(setting_name_buf); + + // fall back to global value + if (tab_width == NULL) { + tab_width = settings_get("editor.tab-width"); + } + l.tab_width = tab_width != NULL ? tab_width->value.number_value : 4; + + snprintf(b, 128 - offs, "%s", "lang-srv"); + struct setting *lang_srv = settings_get(setting_name_buf); + l.lang_srv = lang_srv->value.string_value; + + return l; +} + +void next_ext(const char *curr, const char **nxt, const char **end) { + if (curr == NULL) { + *nxt = *end = NULL; + return; + } + + *nxt = curr; + *end = curr + strlen(curr); + + const char *spc = strchr(curr, ' '); + if (spc != NULL) { + *end = spc; + } +} + +struct language lang_from_extension(const char *ext) { + + uint32_t extlen = strlen(ext); + if (extlen == 0) { + return g_fundamental; + } + + // get "languages.*" settings + struct setting **settings = NULL; + uint32_t nsettings = 0; + settings_get_prefix("languages.", &settings, &nsettings); + + // find the first one with a matching extension list + for (uint32_t i = 0; i < nsettings; ++i) { + struct setting *setting = settings[i]; + char *setting_name = strrchr(setting->path, '.'); + if (setting_name != NULL && + strncmp(setting_name + 1, "extensions", 10) == 0) { + const char *val = setting->value.string_value; + + // go over extensions + const char *cext = val, *nxt = NULL, *end = NULL; + next_ext(cext, &nxt, &end); + while (nxt != end) { + if (extlen == (end - nxt) && strncmp(ext, nxt, (end - nxt)) == 0) { + char lang_path[128] = {0}; + strncpy(lang_path, setting->path, setting_name - setting->path); + + free(settings); + return lang_from_settings(lang_path); + } + + cext = end + 1; + next_ext(cext, &nxt, &end); + } + } + } + + free(settings); + + // fall back to fundamental + return g_fundamental; +} + +struct language lang_from_id(const char *id) { + if (id == NULL || (strlen(id) == 3 && strncmp(id, "fnd", 3) == 0) || + strlen(id) == 0) { + return g_fundamental; + } + + char lang_path[128] = {0}; + snprintf(lang_path, 128, "languages.%s", id); + + // check that it exists + struct setting **settings = NULL; + uint32_t nsettings = 0; + + settings_get_prefix(lang_path, &settings, &nsettings); + free(settings); + + if (nsettings > 0) { + return lang_from_settings(lang_path); + } else { + minibuffer_echo_timeout(4, "failed to find language \"%s\"", id); + return lang_from_settings("languages.fnd"); + } +} diff --git a/src/lang.h b/src/lang.h new file mode 100644 index 0000000..984e207 --- /dev/null +++ b/src/lang.h @@ -0,0 +1,46 @@ +#ifndef _LANG_H +#define _LANG_H + +#include <stdbool.h> +#include <stdint.h> + +/** + * Settings for a programming language. + */ +struct language { + /** Descriptive name of the programming language */ + const char *name; + + /** Tab width for indentation */ + uint32_t tab_width; + + /** Path to the language server */ + const char *lang_srv; +}; + +/** + * Initialize languages. + * + * @param[in] register_default Set to true to register some well known + * languages. + */ +void languages_init(bool register_default); + +/** + * Get a language config by file name extension. + * + * @param[in] ext File extension + * @returns A language config instance or the default language if not found. + */ +struct language lang_from_extension(const char *ext); + +/** + * Get a language config by id. The language id is a short (all-lowercase) + * string identifying the language. + * + * @param[in] id The language id. + * @returns A language config instance or the default language if not found. + */ +struct language lang_from_id(const char *id); + +#endif @@ -11,6 +11,7 @@ #include "buffer.h" #include "buffers.h" #include "display.h" +#include "lang.h" #include "minibuffer.h" #include "reactor.h" #include "settings.h" @@ -86,6 +87,7 @@ int main(int argc, char *argv[]) { signal(SIGTERM, terminate); settings_init(64); + languages_init(true); buffer_static_init(); frame_allocator = frame_allocator_create(16 * 1024 * 1024); diff --git a/src/settings.c b/src/settings.c index c00fd94..4bbcf44 100644 --- a/src/settings.c +++ b/src/settings.c @@ -26,6 +26,16 @@ void settings_destroy() { HASHMAP_DESTROY(&g_settings.settings); } +void setting_set_value(struct setting *setting, struct setting_value val) { + if (setting->value.type == val.type) { + if (setting->value.type == Setting_String && val.string_value != NULL) { + setting->value.string_value = strdup(val.string_value); + } else { + setting->value = val; + } + } +} + void settings_register_setting(const char *path, struct setting_value default_value) { HASHMAP_APPEND(&g_settings.settings, struct setting_entry, path, @@ -33,7 +43,8 @@ void settings_register_setting(const char *path, if (s != NULL) { struct setting *new_setting = &s->value; - new_setting->value = default_value; + new_setting->value.type = default_value.type; + setting_set_value(new_setting, default_value); strncpy(new_setting->path, path, 128); new_setting->path[127] = '\0'; } @@ -64,15 +75,15 @@ void settings_get_prefix(const char *prefix, struct setting **settings_out[], void settings_set(const char *path, struct setting_value value) { struct setting *setting = settings_get(path); - if (setting != NULL && setting->value.type == value.type) { - setting->value = value; + if (setting != NULL) { + setting_set_value(setting, value); } } void setting_to_string(struct setting *setting, char *buf, size_t n) { switch (setting->value.type) { case Setting_Bool: - snprintf(buf, n, "%s", setting->value.bool_value ? "true" : false); + snprintf(buf, n, "%s", setting->value.bool_value ? "true" : "false"); break; case Setting_Number: snprintf(buf, n, "%ld", setting->value.number_value); @@ -105,7 +116,7 @@ int32_t settings_set_cmd(struct command_ctx ctx, int argc, const char *argv[]) { if (argc == 0) { return minibuffer_prompt(ctx, "setting: "); } else if (argc == 1) { - // validate setting here as well + // validate setting here as well for a better experience struct setting *setting = settings_get(argv[0]); if (setting == NULL) { minibuffer_echo_timeout(4, "no such setting \"%s\"", argv[0]); @@ -125,17 +136,19 @@ int32_t settings_set_cmd(struct command_ctx ctx, int argc, const char *argv[]) { struct setting_value new_value = {.type = setting->value.type}; switch (setting->value.type) { case Setting_Bool: - new_value.bool_value = strncmp("true", value, 4) == 0; + new_value.bool_value = strncmp("true", value, 4) == 0 || + strncmp("yes", value, 3) == 0 || + strncmp("on", value, 2) == 0; break; case Setting_Number: new_value.number_value = atol(value); break; case Setting_String: - new_value.string_value = strdup(value); + new_value.string_value = (char *)value; break; } - setting->value = new_value; + setting_set_value(setting, new_value); } return 0; |
