#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