summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlbert Cervin <albert@acervin.com>2023-03-17 08:43:41 +0100
committerAlbert Cervin <albert@acervin.com>2023-03-17 08:43:41 +0100
commit0e61e64b4e1036f2bf107efb01bea1017893d5e6 (patch)
treeafed627a8e4a0d3136e6f5ff3e6ca1a1a7274ca6 /src
parent40db61eb7a2019ced97f09a9687139f35749f4e0 (diff)
downloaddged-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.c31
-rw-r--r--src/buffer.h4
-rw-r--r--src/lang.c160
-rw-r--r--src/lang.h46
-rw-r--r--src/main.c2
-rw-r--r--src/settings.c29
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
diff --git a/src/main.c b/src/main.c
index 31305d9..427f677 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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;