#include "ecex.h" #include #define C_TOOLS_PLUGIN_ID "c-tools" static int c_tools_append_char(char *out, int out_cap, int *used, char ch) { if (!out || !used || out_cap <= 0 || *used < 0 || *used + 1 >= out_cap) return -1; out[*used] = ch; *used = *used + 1; out[*used] = '\0'; return 0; } static int c_tools_append_text(char *out, int out_cap, int *used, const char *text) { if (!text) text = ""; for (int i = 0; text[i]; i++) { if (c_tools_append_char(out, out_cap, used, text[i]) != 0) return -1; } return 0; } static int c_tools_copy_text(char *out, int out_cap, const char *text) { int used = 0; if (!out || out_cap <= 0) return -1; out[0] = '\0'; return c_tools_append_text(out, out_cap, &used, text); } static int c_tools_shell_quote(const char *input, char *out, int out_cap) { int used = 0; if (!input || !out || out_cap < 3) return -1; out[0] = '\0'; if (c_tools_append_char(out, out_cap, &used, '\'') != 0) return -1; for (int i = 0; input[i]; i++) { if (input[i] == '\'') { if (c_tools_append_text(out, out_cap, &used, "'\\''") != 0) return -1; } else { if (c_tools_append_char(out, out_cap, &used, input[i]) != 0) return -1; } } if (c_tools_append_char(out, out_cap, &used, '\'') != 0) return -1; return 0; } static buffer_t *c_tools_current_path_buffer(ecex_t *ed) { buffer_t *buffer = ecex_current_buffer(ed); if (!buffer || !buffer->path || !buffer->path[0]) { ecex_message(ed, "C tooling needs a file-backed buffer"); return 0; } return buffer; } static int c_tools_ecex_include_flags(char *out, int out_cap, int force) { if (!out || out_cap <= 0) return -1; out[0] = '\0'; char cwd[4096]; if (ecex_path_cwd(cwd, (size_t)sizeof(cwd)) != 0 || !cwd[0]) { if (c_tools_copy_text(cwd, (int)sizeof(cwd), ".") != 0) return -1; } char include_dir[8192]; int used = 0; include_dir[0] = '\0'; if (c_tools_append_text(include_dir, (int)sizeof(include_dir), &used, cwd) != 0) return -1; if (c_tools_append_text(include_dir, (int)sizeof(include_dir), &used, "/include") != 0) return -1; char header[8192]; used = 0; header[0] = '\0'; if (c_tools_append_text(header, (int)sizeof(header), &used, include_dir) != 0) return -1; if (c_tools_append_text(header, (int)sizeof(header), &used, "/ecex.h") != 0) return -1; if (!force && !ecex_path_exists(header)) return 0; char quoted[8192]; if (c_tools_shell_quote(include_dir, quoted, (int)sizeof(quoted)) != 0) return -1; used = 0; if (c_tools_append_text(out, out_cap, &used, " -I") != 0) return -1; if (c_tools_append_text(out, out_cap, &used, quoted) != 0) return -1; return 0; } static int c_tools_command_lsp_check(const char *quoted, char *out, int out_cap) { int used = 0; if (!quoted || !out || out_cap <= 0) return -1; out[0] = '\0'; if (c_tools_append_text(out, out_cap, &used, "clangd --check=") != 0) return -1; if (c_tools_append_text(out, out_cap, &used, quoted) != 0) return -1; return 0; } static int c_tools_command_lint(const char *quoted, const char *include_flags, char *out, int out_cap) { int used = 0; if (!quoted || !include_flags || !out || out_cap <= 0) return -1; out[0] = '\0'; if (c_tools_append_text(out, out_cap, &used, "${CC:-clang} -fsyntax-only -Wall -Wextra") != 0) return -1; if (c_tools_append_text(out, out_cap, &used, include_flags) != 0) return -1; if (c_tools_append_char(out, out_cap, &used, ' ') != 0) return -1; if (c_tools_append_text(out, out_cap, &used, quoted) != 0) return -1; return 0; } static int cmd_c_lsp_check(ecex_t *ed) { buffer_t *buffer = c_tools_current_path_buffer(ed); if (!buffer) return -1; char quoted[4096]; char command[8192]; if (c_tools_shell_quote(buffer->path, quoted, (int)sizeof(quoted)) != 0) { ecex_message(ed, "Could not quote C buffer path"); return -1; } if (c_tools_command_lsp_check(quoted, command, (int)sizeof(command)) != 0) { ecex_message(ed, "Could not build clangd command"); return -1; } return ecex_compile(ed, command); } static int cmd_c_lint_buffer(ecex_t *ed) { buffer_t *buffer = c_tools_current_path_buffer(ed); if (!buffer) return -1; char quoted[4096]; char include_flags[8192]; char command[12288]; if (c_tools_shell_quote(buffer->path, quoted, (int)sizeof(quoted)) != 0) { ecex_message(ed, "Could not quote C buffer path"); return -1; } if (c_tools_ecex_include_flags(include_flags, (int)sizeof(include_flags), 0) != 0) { ecex_message(ed, "Could not build ecex include flags"); return -1; } if (c_tools_command_lint(quoted, include_flags, command, (int)sizeof(command)) != 0) { ecex_message(ed, "Could not build C lint command"); return -1; } return ecex_compile(ed, command); } static int cmd_c_ecex_lint_buffer(ecex_t *ed) { buffer_t *buffer = c_tools_current_path_buffer(ed); if (!buffer) return -1; char quoted[4096]; char include_flags[8192]; char command[12288]; if (c_tools_shell_quote(buffer->path, quoted, (int)sizeof(quoted)) != 0) { ecex_message(ed, "Could not quote C buffer path"); return -1; } if (c_tools_ecex_include_flags(include_flags, (int)sizeof(include_flags), 1) != 0) { ecex_message(ed, "Could not build ecex include flags"); return -1; } if (c_tools_command_lint(quoted, include_flags, command, (int)sizeof(command)) != 0) { ecex_message(ed, "Could not build C lint command"); return -1; } return ecex_compile(ed, command); } static int cmd_c_tools_status(ecex_t *ed) { buffer_t *buffer = ecex_create_interactive_buffer(ed, "*c-tools*"); if (!buffer) return -1; buffer_clear(buffer); buffer_append(buffer, "C tools\n\n"); buffer_append(buffer, "C-x c l runs clangd --check for the current file.\n"); buffer_append(buffer, "C-x c k runs ${CC:-clang} -fsyntax-only -Wall -Wextra, adding ./include when ecex.h exists.\n"); buffer_append(buffer, "C-x c e runs the same lint with ./include forced for ecex development.\n"); buffer_append(buffer, "Both commands use the file on disk; save first for exact diagnostics.\n"); buffer->modified = 0; return ecex_switch_buffer(ed, "*c-tools*"); } ECEX_PLUGIN_BEGIN(ecex_c_tools_plugin, C_TOOLS_PLUGIN_ID) if (ecex_plugin_require_dependency(ed, C_TOOLS_PLUGIN_ID, "clang") != 0) return 0; if (ecex_plugin_require_dependency(ed, C_TOOLS_PLUGIN_ID, "clangd") != 0) return 0; ECEX_CONFIG_COMMAND("c-lsp-check", cmd_c_lsp_check); ECEX_CONFIG_COMMAND("c-lint-buffer", cmd_c_lint_buffer); ECEX_CONFIG_COMMAND("c-ecex-lint-buffer", cmd_c_ecex_lint_buffer); ECEX_CONFIG_COMMAND("c-tools-status", cmd_c_tools_status); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-x c l", "c-lsp-check")); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-x c k", "c-lint-buffer")); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-x c e", "c-ecex-lint-buffer")); ECEX_CONFIG_TRY(ecex_bind_mode_key(ed, "c-mode", "C-x c s", "c-tools-status")); return 0; ECEX_PLUGIN_END #ifndef ECEX_NO_STANDALONE_CONFIG ECEX_CONFIG_BEGIN ECEX_CONFIG_INCLUDE(ecex_c_tools_plugin); ECEX_CONFIG_END #endif