aboutsummaryrefslogtreecommitdiff
path: root/config/c_mode_plugin.c
diff options
context:
space:
mode:
authorDavid Moc <personal@cdatgoose.org>2026-06-02 13:50:21 +0200
committerDavid Moc <personal@cdatgoose.org>2026-06-02 13:50:21 +0200
commita15cb041654ae307add0b998b526c87c3f42bf5f (patch)
tree225bb4b70e9fa05aa5f4d2722a1a9cf5fc6fca7f /config/c_mode_plugin.c
parent6aeaa171dc1ca43392f53cbd02097f76e1b1c5a0 (diff)
Add plugin hooks and mode plugins
Diffstat (limited to 'config/c_mode_plugin.c')
-rw-r--r--config/c_mode_plugin.c192
1 files changed, 192 insertions, 0 deletions
diff --git a/config/c_mode_plugin.c b/config/c_mode_plugin.c
new file mode 100644
index 0000000..e707bca
--- /dev/null
+++ b/config/c_mode_plugin.c
@@ -0,0 +1,192 @@
+#include "ecex.h"
+
+#include <string.h>
+
+#define C_MODE_PLUGIN_ID "c-mode"
+#define C_MODE_LSP_PROVIDER_NAME "c-mode-clangd"
+#define C_MODE_SLOT_CLANGD "clangd"
+
+static int c_mode_line_starts_with(buffer_t *buffer, size_t first, size_t line_end, const char *word) {
+ size_t len = strlen(word);
+ if (!buffer || !word || first + len > line_end) return 0;
+ if (strncmp(buffer->data + first, word, len) != 0) return 0;
+ if (first + len == line_end) return 1;
+ char c = buffer->data[first + len];
+ return !(c == '_' ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9'));
+}
+
+static int c_mode_line_ends_with_colon(buffer_t *buffer, size_t first, size_t line_end) {
+ if (!buffer || first >= line_end) return 0;
+ size_t p = line_end;
+ while (p > first && (buffer->data[p - 1] == ' ' || buffer->data[p - 1] == '\t')) p--;
+ return p > first && buffer->data[p - 1] == ':';
+}
+
+static size_t c_mode_previous_nonblank_line(buffer_t *buffer, size_t line_start) {
+ if (!buffer || line_start == 0) return (size_t)-1;
+
+ size_t end = line_start - 1;
+ while (end > 0 && buffer->data[end - 1] == '\n') end--;
+
+ for (;;) {
+ size_t start = buffer_line_start_at(buffer, end);
+ size_t line_end = buffer_line_end_at(buffer, start);
+ size_t p = start;
+ while (p < line_end && (buffer->data[p] == ' ' || buffer->data[p] == '\t')) p++;
+ if (p < line_end) return start;
+ if (start == 0) break;
+ end = start - 1;
+ }
+
+ return (size_t)-1;
+}
+
+static int c_mode_brace_depth_before(buffer_t *buffer, size_t limit) {
+ int depth = 0;
+ int in_block = 0;
+ int in_line = 0;
+ int in_string = 0;
+ int in_char = 0;
+ int escaped = 0;
+
+ if (!buffer || !buffer->data) return 0;
+ if (limit > buffer->len) limit = buffer->len;
+
+ for (size_t i = 0; i < limit; i++) {
+ char c = buffer->data[i];
+ char next = i + 1 < limit ? buffer->data[i + 1] : '\0';
+
+ if (in_line) {
+ if (c == '\n') in_line = 0;
+ continue;
+ }
+
+ if (in_block) {
+ if (c == '*' && next == '/') {
+ in_block = 0;
+ i++;
+ }
+ continue;
+ }
+
+ if (in_string) {
+ if (escaped) escaped = 0;
+ else if (c == '\\') escaped = 1;
+ else if (c == '"') in_string = 0;
+ else if (c == '\n') in_string = 0;
+ continue;
+ }
+
+ if (in_char) {
+ if (escaped) escaped = 0;
+ else if (c == '\\') escaped = 1;
+ else if (c == '\'') in_char = 0;
+ else if (c == '\n') in_char = 0;
+ continue;
+ }
+
+ if (c == '/' && next == '/') {
+ in_line = 1;
+ i++;
+ } else if (c == '/' && next == '*') {
+ in_block = 1;
+ i++;
+ } else if (c == '"') {
+ in_string = 1;
+ } else if (c == '\'') {
+ in_char = 1;
+ } else if (c == '{') {
+ depth++;
+ } else if (c == '}') {
+ if (depth > 0) depth--;
+ }
+ }
+
+ return depth;
+}
+
+static int cmd_c_indent_line(ecex_t *ed) {
+ if (!ed) return -1;
+ buffer_t *buffer = ecex_current_buffer(ed);
+ if (!buffer || buffer->read_only || buffer_is_interactive(buffer)) return -1;
+
+ size_t line_start = buffer_current_line_start(buffer);
+ size_t line_end = buffer_current_line_end(buffer);
+ size_t first = line_start;
+ while (first < line_end && (buffer->data[first] == ' ' || buffer->data[first] == '\t')) first++;
+
+ if (first < line_end && buffer->data[first] == '#') return ecex_indent_line_to(buffer, 0);
+
+ int depth = c_mode_brace_depth_before(buffer, line_start);
+ int target = depth * 4;
+
+ if (first < line_end && buffer->data[first] == '}') target -= 4;
+
+ size_t prev = c_mode_previous_nonblank_line(buffer, line_start);
+ if (prev != (size_t)-1) {
+ size_t prev_end = buffer_line_end_at(buffer, prev);
+ size_t prev_first = prev;
+ while (prev_first < prev_end && (buffer->data[prev_first] == ' ' || buffer->data[prev_first] == '\t')) prev_first++;
+
+ if ((c_mode_line_starts_with(buffer, prev_first, prev_end, "case") ||
+ c_mode_line_starts_with(buffer, prev_first, prev_end, "default")) &&
+ c_mode_line_ends_with_colon(buffer, prev_first, prev_end) &&
+ !(c_mode_line_starts_with(buffer, first, line_end, "case") ||
+ c_mode_line_starts_with(buffer, first, line_end, "default") ||
+ (first < line_end && buffer->data[first] == '}'))) {
+ target += 4;
+ }
+ }
+
+ if (target < 0) target = 0;
+ return ecex_indent_line_to(buffer, target);
+}
+
+static int cmd_c_complete(ecex_t *ed) {
+ return ecex_complete_at_point(ed);
+}
+
+static int c_mode_file_handler(ecex_t *ed, buffer_t *buffer) {
+ if (!ed || !buffer) return -1;
+ return ecex_buffer_set_major_mode_by_name(ed, buffer, "c-mode");
+}
+
+ECEX_PLUGIN_BEGIN(ecex_c_mode_plugin, C_MODE_PLUGIN_ID)
+ if (!ecex_define_major_mode(ed, "c-mode")) return -1;
+
+ ECEX_CONFIG_COMMAND("c-indent-line", cmd_c_indent_line);
+ ECEX_CONFIG_COMMAND("c-complete", cmd_c_complete);
+ ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "TAB", "c-indent-line"));
+ ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-TAB", "c-complete"));
+ ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-S-TAB", "complete-at-point-previous"));
+
+ if (ecex_dependency_available("clangd")) {
+ ECEX_CONFIG_TRY(ecex_add_clangd_completion_provider(ed,
+ C_MODE_LSP_PROVIDER_NAME,
+ "c-mode"));
+ ecex_plugin_slot_i32_set_scalar(plugin, C_MODE_SLOT_CLANGD, 1);
+ } else {
+ ecex_plugin_slot_i32_set_scalar(plugin, C_MODE_SLOT_CLANGD, 0);
+ ecex_message(ed, "c-mode: clangd not found; C completion disabled");
+ }
+
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".c", c_mode_file_handler));
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".h", c_mode_file_handler));
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".cc", c_mode_file_handler));
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".cpp", c_mode_file_handler));
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".cxx", c_mode_file_handler));
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".hh", c_mode_file_handler));
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".hpp", c_mode_file_handler));
+ ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".hxx", c_mode_file_handler));
+
+ return 0;
+ECEX_PLUGIN_END
+
+#ifndef ECEX_NO_STANDALONE_CONFIG
+ECEX_CONFIG_BEGIN
+ ECEX_CONFIG_INCLUDE(ecex_c_mode_plugin);
+ECEX_CONFIG_END
+#endif