From a15cb041654ae307add0b998b526c87c3f42bf5f Mon Sep 17 00:00:00 2001 From: David Moc Date: Tue, 2 Jun 2026 13:50:21 +0200 Subject: Add plugin hooks and mode plugins --- config/c_mode_plugin.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 config/c_mode_plugin.c (limited to 'config/c_mode_plugin.c') 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 + +#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 -- cgit v1.2.3