#include "ecex.h" #include #include #define C_MODE_PLUGIN_ID "c-mode" #define C_MODE_LSP_PROVIDER_NAME "c-mode-clangd" #define C_MODE_SLOT_CLANGD "clangd" #define C_MODE_TAB_WIDTH_DEFAULT 4 static int c_mode_indent_width(void) { int width = ecex_c_mode_tab_width(); return width > 0 ? width : C_MODE_TAB_WIDTH_DEFAULT; } 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 c_mode_line_indent_cols(buffer_t *buffer, size_t line_start, size_t line_end) { int cols = 0; if (!buffer) return 0; for (size_t i = line_start; i < line_end; i++) { if (buffer->data[i] == ' ') { cols++; } else if (buffer->data[i] == '\t') { int width = c_mode_indent_width(); cols += width - (cols % width); } else { break; } } return cols; } 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 width = c_mode_indent_width(); int depth = c_mode_brace_depth_before(buffer, line_start); int target = depth * width; if (first < line_end && buffer->data[first] == '}') target -= width; 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 += width; } } if (target < 0) target = 0; return ecex_indent_line_to(buffer, target); } static int cmd_c_newline_and_indent(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; if (buffer_insert_char(buffer, '\n') != 0) return -1; return cmd_c_indent_line(ed); } static int cmd_c_dedent_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); int current = c_mode_line_indent_cols(buffer, line_start, line_end); int target = current - c_mode_indent_width(); 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_set_tab_width_command(ecex_t *ed, int width) { int actual = ecex_c_mode_set_tab_width(width); char message[64]; snprintf(message, sizeof(message), "c-mode tab width: %d", actual); ecex_message(ed, message); return 0; } static int cmd_c_tab_width_2(ecex_t *ed) { return c_mode_set_tab_width_command(ed, 2); } static int cmd_c_tab_width_4(ecex_t *ed) { return c_mode_set_tab_width_command(ed, 4); } static int cmd_c_tab_width_8(ecex_t *ed) { return c_mode_set_tab_width_command(ed, 8); } static int c_mode_path_char(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.' || c == '/' || c == '~' || c == '+'; } static int c_mode_copy_range(buffer_t *buffer, size_t start, size_t end, char *out, size_t out_size) { if (!buffer || !out || out_size == 0 || start > end || end > buffer->len) return -1; size_t len = end - start; if (len == 0) return -1; if (len >= out_size) len = out_size - 1; memcpy(out, buffer->data + start, len); out[len] = '\0'; return 0; } static int c_mode_line_is_include(buffer_t *buffer, size_t line_start, size_t line_end) { if (!buffer) return 0; size_t p = line_start; while (p < line_end && (buffer->data[p] == ' ' || buffer->data[p] == '\t')) p++; return p + 8 <= line_end && strncmp(buffer->data + p, "#include", 8) == 0; } static int c_mode_quoted_path_at_point(buffer_t *buffer, size_t line_start, size_t line_end, size_t point, char *out, size_t out_size) { int include_line = c_mode_line_is_include(buffer, line_start, line_end); for (size_t p = line_start; p < line_end; p++) { char open = buffer->data[p]; char close = '\0'; if (open == '"') close = '"'; else if (include_line && open == '<') close = '>'; else continue; size_t q = p + 1; while (q < line_end && buffer->data[q] != close) q++; if (q >= line_end) return -1; if (point > p && point <= q) return c_mode_copy_range(buffer, p + 1, q, out, out_size); p = q; } return -1; } static int c_mode_path_at_point(buffer_t *buffer, char *out, size_t out_size) { if (out && out_size) out[0] = '\0'; if (!buffer || !out || out_size == 0 || !buffer->data) return -1; size_t point = buffer->point > buffer->len ? buffer->len : buffer->point; size_t line_start = buffer_line_start_at(buffer, point); size_t line_end = buffer_line_end_at(buffer, point); if (c_mode_quoted_path_at_point(buffer, line_start, line_end, point, out, out_size) == 0) { return 0; } size_t pos = point; if (pos == buffer->len || (pos < buffer->len && !c_mode_path_char(buffer->data[pos]))) { if (pos == 0 || !c_mode_path_char(buffer->data[pos - 1])) return -1; pos--; } size_t start = pos; while (start > line_start && c_mode_path_char(buffer->data[start - 1])) start--; size_t end = pos; while (end < line_end && c_mode_path_char(buffer->data[end])) end++; return c_mode_copy_range(buffer, start, end, out, out_size); } static int cmd_c_find_file_at_point(ecex_t *ed) { if (!ed) return -1; buffer_t *buffer = ecex_current_buffer(ed); if (!buffer) return -1; char path[1024]; if (c_mode_path_at_point(buffer, path, sizeof(path)) != 0) { ecex_message(ed, "No file at point"); return 0; } return ecex_find_project_file(ed, buffer->path, path); } static int cmd_c_jump_to_definition(ecex_t *ed) { return ecex_clangd_jump_to_definition(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-newline-and-indent", cmd_c_newline_and_indent); ECEX_CONFIG_COMMAND("c-dedent-line", cmd_c_dedent_line); ECEX_CONFIG_COMMAND("c-complete", cmd_c_complete); ECEX_CONFIG_COMMAND("c-tab-width-2", cmd_c_tab_width_2); ECEX_CONFIG_COMMAND("c-tab-width-4", cmd_c_tab_width_4); ECEX_CONFIG_COMMAND("c-tab-width-8", cmd_c_tab_width_8); ECEX_CONFIG_COMMAND("c-jump-to-definition", cmd_c_jump_to_definition); ECEX_CONFIG_COMMAND("c-find-file-at-point", cmd_c_find_file_at_point); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "TAB", "c-indent-line")); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "S-TAB", "c-dedent-line")); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "ENTER", "c-newline-and-indent")); 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")); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-g d", "c-jump-to-definition")); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-g f", "c-find-file-at-point")); 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