diff options
| author | David Moc <personal@cdatgoose.org> | 2026-06-02 13:50:21 +0200 |
|---|---|---|
| committer | David Moc <personal@cdatgoose.org> | 2026-06-02 13:50:21 +0200 |
| commit | a15cb041654ae307add0b998b526c87c3f42bf5f (patch) | |
| tree | 225bb4b70e9fa05aa5f4d2722a1a9cf5fc6fca7f /config | |
| parent | 6aeaa171dc1ca43392f53cbd02097f76e1b1c5a0 (diff) | |
Add plugin hooks and mode plugins
Diffstat (limited to 'config')
| -rw-r--r-- | config/c_mode_plugin.c | 192 | ||||
| -rw-r--r-- | config/c_tools_plugin.c | 210 | ||||
| -rw-r--r-- | config/ecex_api_completion_plugin.c | 806 | ||||
| -rw-r--r-- | config/ecexrc.c | 17 | ||||
| -rw-r--r-- | config/markdown_plugin.c | 68 | ||||
| -rw-r--r-- | config/render_demo.c | 118 | ||||
| -rw-r--r-- | config/tetris.c | 188 | ||||
| -rw-r--r-- | config/which_key_plugin.c | 107 |
8 files changed, 1543 insertions, 163 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 diff --git a/config/c_tools_plugin.c b/config/c_tools_plugin.c new file mode 100644 index 0000000..c3c075f --- /dev/null +++ b/config/c_tools_plugin.c @@ -0,0 +1,210 @@ +#include "ecex.h" + +#include <string.h> + +#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 diff --git a/config/ecex_api_completion_plugin.c b/config/ecex_api_completion_plugin.c new file mode 100644 index 0000000..dae5745 --- /dev/null +++ b/config/ecex_api_completion_plugin.c @@ -0,0 +1,806 @@ +#include "ecex.h" + +#include <string.h> + +#define ECEX_API_PLUGIN_ID "ecex-mode" +#define ECEX_API_SYMBOLS_PROVIDER "ecex-mode-symbols" +#define ECEX_API_ED_FIELDS_PROVIDER "ecex-mode-ed-fields" +#define ECEX_API_SLOT_ENABLED "enabled" +#if defined(__GNUC__) || defined(__clang__) +#define ECEX_API_UNUSED __attribute__((unused)) +#else +#define ECEX_API_UNUSED +#endif +#define ECEX_API_SYMBOL_WORDS \ + "ecex_t\n" \ + "buffer_t\n" \ + "ecex_window_t\n" \ + "ecex_draw_context_t\n" \ + "ecex_plugin_t\n" \ + "ecex_plugin_runtime_t\n" \ + "ecex_command_fn\n" \ + "ecex_completion_provider_fn\n" \ + "ecex_file_handler_fn\n" \ + "ECEX_CONFIG_BEGIN\n" \ + "ECEX_CONFIG_END\n" \ + "ECEX_PLUGIN_BEGIN\n" \ + "ECEX_PLUGIN_END\n" \ + "ECEX_CONFIG_TRY\n" \ + "ECEX_CONFIG_COMMAND\n" \ + "ECEX_CONFIG_BIND\n" \ + "ECEX_CONFIG_MODE\n" \ + "ECEX_CONFIG_MODE_BIND\n" \ + "ECEX_CONFIG_INCLUDE\n" \ + "ECEX_RGB8\n" \ + "ECEX_ARRAY_COUNT\n" \ + "ecex_new\n" \ + "ecex_free\n" \ + "ecex_config_alloc\n" \ + "ecex_config_calloc\n" \ + "ecex_config_free\n" \ + "ecex_time_seconds\n" \ + "ecex_log\n" \ + "ecex_logf\n" \ + "ecex_log_group_begin\n" \ + "ecex_log_group_end\n" \ + "ecex_log_int\n" \ + "ecex_log_double\n" \ + "ecex_log_ptr\n" \ + "ecex_log_flush\n" \ + "ecex_mem_zero\n" \ + "ecex_i32_get\n" \ + "ecex_i32_set\n" \ + "ecex_prng_next_bounded\n" \ + "ecex_random_bounded\n" \ + "ecex_plugin_register\n" \ + "ecex_plugin_require\n" \ + "ecex_plugin_find\n" \ + "ecex_plugin_id\n" \ + "ecex_plugin_slot_alloc\n" \ + "ecex_plugin_slot_get\n" \ + "ecex_plugin_slot_free\n" \ + "ecex_plugin_slot_set_export_flags\n" \ + "ecex_plugin_slot_read_exported\n" \ + "ecex_plugin_slot_i32_get_scalar\n" \ + "ecex_plugin_slot_i32_get\n" \ + "ecex_plugin_slot_i32_set\n" \ + "ecex_plugin_slot_i32_get_2d\n" \ + "ecex_plugin_slot_i32_set_2d\n" \ + "ecex_plugin_slot_i32_set_scalar\n" \ + "ecex_plugin_object_alloc\n" \ + "ecex_plugin_object_calloc\n" \ + "ecex_plugin_object_free\n" \ + "ecex_plugin_object_valid\n" \ + "ecex_plugin_object_i32_get\n" \ + "ecex_plugin_object_i32_set\n" \ + "ecex_plugin_object_ptr_get\n" \ + "ecex_plugin_object_ptr_set\n" \ + "ecex_plugin_text_set\n" \ + "ecex_plugin_text_set_from_buffer_title\n" \ + "ecex_plugin_text_free\n" \ + "ecex_plugin_text_free_all\n" \ + "ecex_plugin_text_get_drawable\n" \ + "ecex_plugin_file_handler_register\n" \ + "ecex_plugin_file_handlers_run\n" \ + "ecex_buffer_text_len\n" \ + "ecex_buffer_scroll_line_index\n" \ + "ecex_buffer_line_count_int\n" \ + "ecex_buffer_line_copy_text\n" \ + "ecex_run_plugin_file_handlers\n" \ + "ecex_reserve_buffers\n" \ + "ecex_add_buffer\n" \ + "ecex_create_buffer\n" \ + "ecex_find_buffer\n" \ + "ecex_switch_buffer\n" \ + "ecex_current_buffer\n" \ + "ecex_other_buffer\n" \ + "ecex_current_window\n" \ + "ecex_window_count\n" \ + "ecex_sync_current_buffer\n" \ + "ecex_split_window_vertically\n" \ + "ecex_split_window_horizontally\n" \ + "ecex_other_window\n" \ + "ecex_previous_window\n" \ + "ecex_delete_window\n" \ + "ecex_delete_other_windows\n" \ + "ecex_balance_windows\n" \ + "ecex_next_buffer\n" \ + "ecex_previous_buffer\n" \ + "ecex_kill_buffer\n" \ + "ecex_kill_buffer_force\n" \ + "ecex_has_modified_buffers\n" \ + "ecex_validate_bindings\n" \ + "ecex_keep_jit_module\n" \ + "ecex_set_config_path\n" \ + "ecex_config_path\n" \ + "ecex_reload_config\n" \ + "ecex_config_register_commands\n" \ + "ecex_config_bind_keys\n" \ + "ecex_config_bind_mode_keys\n" \ + "ecex_config_define_modes\n" \ + "ecex_apply_theme\n" \ + "ecex_register_command\n" \ + "ecex_execute_command\n" \ + "ecex_add_command_hook\n" \ + "ecex_remove_command_hook\n" \ + "ecex_add_prefix_hook\n" \ + "ecex_remove_prefix_hook\n" \ + "ecex_notify_prefix_hooks\n" \ + "ecex_add_buffer_hook\n" \ + "ecex_remove_buffer_hook\n" \ + "ecex_notify_buffer_hooks\n" \ + "ecex_message\n" \ + "ecex_dependency_available\n" \ + "ecex_plugin_require_dependency\n" \ + "ecex_add_completion_provider\n" \ + "ecex_add_word_completion_provider\n" \ + "ecex_define_word_completion_provider\n" \ + "ecex_completion_provider_add_word\n" \ + "ecex_completion_provider_add_words\n" \ + "ecex_completion_provider_set_detail\n" \ + "ecex_add_clangd_completion_provider\n" \ + "ecex_remove_completion_provider\n" \ + "ecex_buffer_identifier_prefix\n" \ + "ecex_complete_at_point\n" \ + "ecex_set_clipboard_callbacks\n" \ + "ecex_clipboard_get\n" \ + "ecex_clipboard_set\n" \ + "ecex_bind_key\n" \ + "ecex_lookup_key\n" \ + "ecex_define_major_mode\n" \ + "ecex_major_mode_by_name\n" \ + "ecex_major_mode_name\n" \ + "ecex_buffer_set_major_mode\n" \ + "ecex_buffer_set_major_mode_by_name\n" \ + "ecex_buffer_major_mode_name\n" \ + "ecex_bind_mode_key\n" \ + "ecex_lookup_key_for_buffer\n" \ + "ecex_key_sequence_has_prefix_for_buffer\n" \ + "ecex_describe_key_prefix\n" \ + "ecex_auto_set_major_mode\n" \ + "ecex_list_commands\n" \ + "ecex_list_buffers\n" \ + "ecex_create_interactive_buffer\n" \ + "ecex_interactive_append_line\n" \ + "ecex_interactive_activate_current_line\n" \ + "ecex_buffer_set_renderer\n" \ + "ecex_buffer_clear_renderer\n" \ + "ecex_buffer_has_renderer\n" \ + "ecex_buffer_renderer_userdata\n" \ + "ecex_buffer_set_mouse_handler\n" \ + "ecex_buffer_clear_mouse_handler\n" \ + "ecex_buffer_has_mouse_handler\n" \ + "ecex_buffer_mouse_userdata\n" \ + "ecex_buffer_set_animation\n" \ + "ecex_buffer_set_animation_ms\n" \ + "ecex_buffer_clear_animation\n" \ + "ecex_buffer_is_animating\n" \ + "ecex_buffer_animation_userdata\n" \ + "ecex_tick_animations\n" \ + "ecex_buffer_replace_text\n" \ + "ecex_buffer_set_modified\n" \ + "ecex_draw_set_color\n" \ + "ecex_draw_rect\n" \ + "ecex_draw_rect_outline\n" \ + "ecex_draw_line\n" \ + "ecex_draw_text\n" \ + "ecex_draw_text_aligned\n" \ + "ecex_draw_text_width\n" \ + "ecex_draw_rgba\n" \ + "ecex_draw_color_rgba8_i\n" \ + "ecex_draw_rect_i\n" \ + "ecex_draw_rect_outline_i\n" \ + "ecex_draw_line_i\n" \ + "ecex_draw_text_i\n" \ + "ecex_draw_plugin_text_i\n" \ + "ecex_draw_plugin_text_rect_i\n" \ + "ecex_draw_markdown_canvas_i\n" \ + "ecex_draw_markdown_text_i\n" \ + "ecex_draw_markdown_canvas_auto_i\n" \ + "ecex_draw_markdown_line_auto_i\n" \ + "ecex_draw_label_i\n" \ + "ecex_draw_stat_i\n" \ + "ecex_draw_tetris_preview_i\n" \ + "ecex_find_file\n" \ + "ecex_save_current_buffer\n" \ + "ecex_write_current_buffer\n" \ + "ecex_compile\n" \ + "ecex_grep\n" \ + "ecex_rerun_compile\n" \ + "ecex_rerun_grep\n" \ + "ecex_next_interactive_action\n" \ + "ecex_previous_interactive_action\n" \ + "ecex_indent_line_to\n" \ + "ecex_comment_region\n" \ + "ecex_uncomment_region\n" \ + "ecex_request_prompt\n" \ + "ecex_clear_prompt_request\n" \ + "ecex_complete_command\n" \ + "ecex_set_font\n" \ + "ecex_get_font_size\n" \ + "ecex_set_font_size\n" \ + "ecex_adjust_font_size\n" \ + "ecex_set_bg_color\n" \ + "ecex_set_fg_color\n" \ + "ecex_set_status_bg_color\n" \ + "ecex_set_status_fg_color\n" \ + "ecex_set_status_border_color\n" \ + "ecex_set_cursor_color\n" \ + "ecex_set_region_bg_color\n" \ + "ecex_set_minibuffer_bg_color\n" \ + "ecex_set_minibuffer_fg_color\n" \ + "ecex_set_completion_fg_color\n" \ + "ecex_set_completion_enabled\n" \ + "ecex_set_interactive_highlight_bg_color\n" \ + "ecex_set_interactive_highlight_fg_color\n" \ + "ecex_set_current_line_bg_color\n" \ + "ecex_set_search_bg_color\n" \ + "ecex_set_line_numbers_enabled\n" \ + "ecex_set_current_line_enabled\n" \ + "buffer_new\n" \ + "buffer_free\n" \ + "buffer_reserve\n" \ + "buffer_clear\n" \ + "buffer_set_text\n" \ + "buffer_insert\n" \ + "buffer_append\n" \ + "buffer_prepend\n" \ + "buffer_insert_at\n" \ + "buffer_insert_char\n" \ + "buffer_delete_range\n" \ + "buffer_delete_selection\n" \ + "buffer_replace_selection\n" \ + "buffer_backspace\n" \ + "buffer_delete_forward\n" \ + "buffer_kill_line\n" \ + "buffer_undo\n" \ + "buffer_redo\n" \ + "buffer_clear_undo\n" \ + "buffer_set_point\n" \ + "buffer_move_left\n" \ + "buffer_move_right\n" \ + "buffer_move_up\n" \ + "buffer_move_down\n" \ + "buffer_move_word_left\n" \ + "buffer_move_word_right\n" \ + "buffer_move_beginning_of_line\n" \ + "buffer_move_end_of_line\n" \ + "buffer_move_beginning_of_buffer\n" \ + "buffer_move_end_of_buffer\n" \ + "buffer_search_forward\n" \ + "buffer_search_backward\n" \ + "buffer_set_mark\n" \ + "buffer_clear_mark\n" \ + "buffer_has_selection\n" \ + "buffer_selection_range\n" \ + "buffer_line_start_at\n" \ + "buffer_line_end_at\n" \ + "buffer_current_line_start\n" \ + "buffer_current_line_end\n" \ + "buffer_current_column\n" \ + "buffer_current_line_number\n" \ + "buffer_line_count\n" \ + "buffer_substring\n" \ + "buffer_current_line_copy\n" \ + "buffer_load_file\n" \ + "buffer_save\n" \ + "buffer_save_as\n" \ + "buffer_set_interactive\n" \ + "buffer_is_interactive\n" \ + "buffer_clear_interactive_actions\n" \ + "buffer_add_interactive_action\n" \ + "buffer_interactive_action_at_line\n" \ + "ecex_path_copy\n" \ + "ecex_path_expand_user\n" \ + "ecex_path_join\n" \ + "ecex_path_dirname\n" \ + "ecex_path_basename_dup\n" \ + "ecex_path_normalize\n" \ + "ecex_path_is_dir\n" \ + "ecex_path_is_file\n" \ + "ecex_path_exists\n" \ + "ecex_path_file_size\n" \ + "ecex_path_is_image\n" \ + "ecex_path_is_previewable_image\n" \ + "ecex_path_is_video\n" \ + "ecex_path_is_media\n" \ + "ecex_path_cwd\n" \ + "ecex_media_open\n" \ + "ecex_media_toggle_playback\n" +#define ECEX_API_ED_FIELD_WORDS \ + "buffers\n" \ + "buffer_cap\n" \ + "buffer_count\n" \ + "current_buffer_index\n" \ + "current_buffer\n" \ + "previous_buffer\n" \ + "windows\n" \ + "window_cap\n" \ + "window_count\n" \ + "current_window_index\n" \ + "jit_modules\n" \ + "jit_module_cap\n" \ + "jit_module_count\n" \ + "commands\n" \ + "command_cap\n" \ + "command_count\n" \ + "keybinds\n" \ + "keybind_cap\n" \ + "keybind_count\n" \ + "mode_keybinds\n" \ + "mode_keybind_cap\n" \ + "mode_keybind_count\n" \ + "command_hooks\n" \ + "command_hook_cap\n" \ + "command_hook_count\n" \ + "prefix_hooks\n" \ + "prefix_hook_cap\n" \ + "prefix_hook_count\n" \ + "buffer_hooks\n" \ + "buffer_hook_cap\n" \ + "buffer_hook_count\n" \ + "completion_providers\n" \ + "completion_provider_cap\n" \ + "completion_provider_count\n" \ + "major_modes\n" \ + "major_mode_cap\n" \ + "major_mode_count\n" \ + "next_major_mode_id\n" \ + "plugins\n" \ + "last_eval_source\n" \ + "last_eval_filename\n" \ + "last_compile_command\n" \ + "last_grep_command\n" \ + "prompt_request\n" \ + "prompt_message\n" \ + "config_path\n" \ + "message\n" \ + "message_revision\n" \ + "clipboard_get\n" \ + "clipboard_set\n" \ + "clipboard_userdata\n" \ + "clipboard_text\n" \ + "should_quit\n" \ + "ui_revision\n" \ + "font_revision\n" \ + "theme\n" + +static const char *ecex_api_symbols[] ECEX_API_UNUSED = { + "ecex_t", + "buffer_t", + "ecex_window_t", + "ecex_draw_context_t", + "ecex_plugin_t", + "ecex_plugin_runtime_t", + "ecex_command_fn", + "ecex_completion_provider_fn", + "ecex_file_handler_fn", + "ECEX_CONFIG_BEGIN", + "ECEX_CONFIG_END", + "ECEX_PLUGIN_BEGIN", + "ECEX_PLUGIN_END", + "ECEX_CONFIG_TRY", + "ECEX_CONFIG_COMMAND", + "ECEX_CONFIG_BIND", + "ECEX_CONFIG_MODE", + "ECEX_CONFIG_MODE_BIND", + "ECEX_CONFIG_INCLUDE", + "ECEX_RGB8", + "ECEX_ARRAY_COUNT", + "ecex_new", + "ecex_free", + "ecex_config_alloc", + "ecex_config_calloc", + "ecex_config_free", + "ecex_time_seconds", + "ecex_log", + "ecex_logf", + "ecex_log_group_begin", + "ecex_log_group_end", + "ecex_log_int", + "ecex_log_double", + "ecex_log_ptr", + "ecex_log_flush", + "ecex_mem_zero", + "ecex_i32_get", + "ecex_i32_set", + "ecex_prng_next_bounded", + "ecex_random_bounded", + "ecex_plugin_register", + "ecex_plugin_require", + "ecex_plugin_find", + "ecex_plugin_id", + "ecex_plugin_slot_alloc", + "ecex_plugin_slot_get", + "ecex_plugin_slot_free", + "ecex_plugin_slot_set_export_flags", + "ecex_plugin_slot_read_exported", + "ecex_plugin_slot_i32_get_scalar", + "ecex_plugin_slot_i32_get", + "ecex_plugin_slot_i32_set", + "ecex_plugin_slot_i32_get_2d", + "ecex_plugin_slot_i32_set_2d", + "ecex_plugin_slot_i32_set_scalar", + "ecex_plugin_object_alloc", + "ecex_plugin_object_calloc", + "ecex_plugin_object_free", + "ecex_plugin_object_valid", + "ecex_plugin_object_i32_get", + "ecex_plugin_object_i32_set", + "ecex_plugin_object_ptr_get", + "ecex_plugin_object_ptr_set", + "ecex_plugin_text_set", + "ecex_plugin_text_set_from_buffer_title", + "ecex_plugin_text_free", + "ecex_plugin_text_free_all", + "ecex_plugin_text_get_drawable", + "ecex_plugin_file_handler_register", + "ecex_plugin_file_handlers_run", + "ecex_buffer_text_len", + "ecex_buffer_scroll_line_index", + "ecex_buffer_line_count_int", + "ecex_buffer_line_copy_text", + "ecex_run_plugin_file_handlers", + "ecex_reserve_buffers", + "ecex_add_buffer", + "ecex_create_buffer", + "ecex_find_buffer", + "ecex_switch_buffer", + "ecex_current_buffer", + "ecex_other_buffer", + "ecex_current_window", + "ecex_window_count", + "ecex_sync_current_buffer", + "ecex_split_window_vertically", + "ecex_split_window_horizontally", + "ecex_other_window", + "ecex_previous_window", + "ecex_delete_window", + "ecex_delete_other_windows", + "ecex_balance_windows", + "ecex_next_buffer", + "ecex_previous_buffer", + "ecex_kill_buffer", + "ecex_kill_buffer_force", + "ecex_has_modified_buffers", + "ecex_validate_bindings", + "ecex_keep_jit_module", + "ecex_set_config_path", + "ecex_config_path", + "ecex_reload_config", + "ecex_config_register_commands", + "ecex_config_bind_keys", + "ecex_config_bind_mode_keys", + "ecex_config_define_modes", + "ecex_apply_theme", + "ecex_register_command", + "ecex_execute_command", + "ecex_add_command_hook", + "ecex_remove_command_hook", + "ecex_add_prefix_hook", + "ecex_remove_prefix_hook", + "ecex_notify_prefix_hooks", + "ecex_message", + "ecex_add_completion_provider", + "ecex_remove_completion_provider", + "ecex_add_clangd_completion_provider", + "ecex_buffer_identifier_prefix", + "ecex_complete_at_point", + "ecex_set_clipboard_callbacks", + "ecex_clipboard_get", + "ecex_clipboard_set", + "ecex_bind_key", + "ecex_lookup_key", + "ecex_define_major_mode", + "ecex_major_mode_by_name", + "ecex_major_mode_name", + "ecex_buffer_set_major_mode", + "ecex_buffer_set_major_mode_by_name", + "ecex_buffer_major_mode_name", + "ecex_bind_mode_key", + "ecex_lookup_key_for_buffer", + "ecex_key_sequence_has_prefix_for_buffer", + "ecex_describe_key_prefix", + "ecex_auto_set_major_mode", + "ecex_list_commands", + "ecex_list_buffers", + "ecex_create_interactive_buffer", + "ecex_interactive_append_line", + "ecex_interactive_activate_current_line", + "ecex_buffer_set_renderer", + "ecex_buffer_clear_renderer", + "ecex_buffer_has_renderer", + "ecex_buffer_renderer_userdata", + "ecex_buffer_set_mouse_handler", + "ecex_buffer_clear_mouse_handler", + "ecex_buffer_has_mouse_handler", + "ecex_buffer_mouse_userdata", + "ecex_buffer_set_animation", + "ecex_buffer_set_animation_ms", + "ecex_buffer_clear_animation", + "ecex_buffer_is_animating", + "ecex_buffer_animation_userdata", + "ecex_tick_animations", + "ecex_buffer_replace_text", + "ecex_buffer_set_modified", + "ecex_draw_set_color", + "ecex_draw_rect", + "ecex_draw_rect_outline", + "ecex_draw_line", + "ecex_draw_text", + "ecex_draw_text_aligned", + "ecex_draw_text_width", + "ecex_draw_rgba", + "ecex_draw_color_rgba8_i", + "ecex_draw_rect_i", + "ecex_draw_rect_outline_i", + "ecex_draw_line_i", + "ecex_draw_text_i", + "ecex_draw_plugin_text_i", + "ecex_draw_plugin_text_rect_i", + "ecex_draw_markdown_canvas_i", + "ecex_draw_markdown_text_i", + "ecex_draw_markdown_canvas_auto_i", + "ecex_draw_markdown_line_auto_i", + "ecex_draw_label_i", + "ecex_draw_stat_i", + "ecex_draw_tetris_preview_i", + "ecex_find_file", + "ecex_save_current_buffer", + "ecex_write_current_buffer", + "ecex_compile", + "ecex_grep", + "ecex_rerun_compile", + "ecex_rerun_grep", + "ecex_next_interactive_action", + "ecex_previous_interactive_action", + "ecex_comment_region", + "ecex_uncomment_region", + "ecex_request_prompt", + "ecex_clear_prompt_request", + "ecex_complete_command", + "ecex_set_font", + "ecex_get_font_size", + "ecex_set_font_size", + "ecex_adjust_font_size", + "ecex_set_bg_color", + "ecex_set_fg_color", + "ecex_set_status_bg_color", + "ecex_set_status_fg_color", + "ecex_set_status_border_color", + "ecex_set_cursor_color", + "ecex_set_region_bg_color", + "ecex_set_minibuffer_bg_color", + "ecex_set_minibuffer_fg_color", + "ecex_set_completion_fg_color", + "ecex_set_completion_enabled", + "ecex_set_interactive_highlight_bg_color", + "ecex_set_interactive_highlight_fg_color", + "ecex_set_current_line_bg_color", + "ecex_set_search_bg_color", + "ecex_set_line_numbers_enabled", + "ecex_set_current_line_enabled", + "buffer_new", + "buffer_free", + "buffer_reserve", + "buffer_clear", + "buffer_set_text", + "buffer_insert", + "buffer_append", + "buffer_prepend", + "buffer_insert_at", + "buffer_insert_char", + "buffer_delete_range", + "buffer_delete_selection", + "buffer_replace_selection", + "buffer_backspace", + "buffer_delete_forward", + "buffer_kill_line", + "buffer_undo", + "buffer_redo", + "buffer_clear_undo", + "buffer_set_point", + "buffer_move_left", + "buffer_move_right", + "buffer_move_up", + "buffer_move_down", + "buffer_move_word_left", + "buffer_move_word_right", + "buffer_move_beginning_of_line", + "buffer_move_end_of_line", + "buffer_move_beginning_of_buffer", + "buffer_move_end_of_buffer", + "buffer_search_forward", + "buffer_search_backward", + "buffer_set_mark", + "buffer_clear_mark", + "buffer_has_selection", + "buffer_selection_range", + "buffer_line_start_at", + "buffer_line_end_at", + "buffer_current_line_start", + "buffer_current_line_end", + "buffer_current_column", + "buffer_current_line_number", + "buffer_line_count", + "buffer_substring", + "buffer_current_line_copy", + "buffer_load_file", + "buffer_save", + "buffer_save_as", + "buffer_set_interactive", + "buffer_is_interactive", + "buffer_clear_interactive_actions", + "buffer_add_interactive_action", + "buffer_interactive_action_at_line", + "ecex_path_copy", + "ecex_path_expand_user", + "ecex_path_join", + "ecex_path_dirname", + "ecex_path_basename_dup", + "ecex_path_normalize", + "ecex_path_is_dir", + "ecex_path_is_file", + "ecex_path_exists", + "ecex_path_file_size", + "ecex_path_is_image", + "ecex_path_is_previewable_image", + "ecex_path_is_video", + "ecex_path_is_media", + "ecex_path_cwd", + "ecex_media_open", + "ecex_media_toggle_playback" +}; + +static const char *ecex_ed_fields[] ECEX_API_UNUSED = { + "buffers", + "buffer_cap", + "buffer_count", + "current_buffer_index", + "current_buffer", + "previous_buffer", + "windows", + "window_cap", + "window_count", + "current_window_index", + "jit_modules", + "jit_module_cap", + "jit_module_count", + "commands", + "command_cap", + "command_count", + "keybinds", + "keybind_cap", + "keybind_count", + "mode_keybinds", + "mode_keybind_cap", + "mode_keybind_count", + "command_hooks", + "command_hook_cap", + "command_hook_count", + "prefix_hooks", + "prefix_hook_cap", + "prefix_hook_count", + "completion_providers", + "completion_provider_cap", + "completion_provider_count", + "major_modes", + "major_mode_cap", + "major_mode_count", + "next_major_mode_id", + "plugins", + "last_eval_source", + "last_eval_filename", + "last_compile_command", + "last_grep_command", + "prompt_request", + "prompt_message", + "config_path", + "message", + "message_revision", + "clipboard_get", + "clipboard_set", + "clipboard_userdata", + "clipboard_text", + "should_quit", + "ui_revision", + "font_revision", + "theme" +}; + +static int ecex_api_enabled(ecex_plugin_t *plugin) { + return ecex_plugin_slot_i32_get_scalar(plugin, ECEX_API_SLOT_ENABLED, 1) != 0; +} + +static int ecex_api_register_providers(ecex_t *ed) { + if (ecex_define_word_completion_provider(ed, + ECEX_API_SYMBOLS_PROVIDER, + 0, + ECEX_COMPLETION_DEFAULT) != 0) { + return -1; + } + + if (ecex_completion_provider_add_words(ed, ECEX_API_SYMBOLS_PROVIDER, ECEX_API_SYMBOL_WORDS) != 0) { + ecex_remove_completion_provider(ed, ECEX_API_SYMBOLS_PROVIDER); + return -1; + } + if (ecex_completion_provider_set_detail(ed, ECEX_API_SYMBOLS_PROVIDER, "ecex API symbol") != 0) { + ecex_remove_completion_provider(ed, ECEX_API_SYMBOLS_PROVIDER); + return -1; + } + + if (ecex_define_word_completion_provider(ed, + ECEX_API_ED_FIELDS_PROVIDER, + 0, + ECEX_COMPLETION_ED_ARROW) != 0) { + ecex_remove_completion_provider(ed, ECEX_API_SYMBOLS_PROVIDER); + return -1; + } + + if (ecex_completion_provider_add_words(ed, ECEX_API_ED_FIELDS_PROVIDER, ECEX_API_ED_FIELD_WORDS) != 0) { + ecex_remove_completion_provider(ed, ECEX_API_SYMBOLS_PROVIDER); + ecex_remove_completion_provider(ed, ECEX_API_ED_FIELDS_PROVIDER); + return -1; + } + if (ecex_completion_provider_set_detail(ed, ECEX_API_ED_FIELDS_PROVIDER, "ecex_t field") != 0) { + ecex_remove_completion_provider(ed, ECEX_API_SYMBOLS_PROVIDER); + ecex_remove_completion_provider(ed, ECEX_API_ED_FIELDS_PROVIDER); + return -1; + } + + return 0; +} + +static void ecex_api_remove_providers(ecex_t *ed) { + ecex_remove_completion_provider(ed, ECEX_API_SYMBOLS_PROVIDER); + ecex_remove_completion_provider(ed, ECEX_API_ED_FIELDS_PROVIDER); +} + +static int cmd_ecex_api_complete(ecex_t *ed) { + return ecex_complete_at_point(ed); +} + +static int cmd_ecex_mode(ecex_t *ed) { + buffer_t *buffer = ecex_current_buffer(ed); + if (!buffer) return -1; + return ecex_buffer_set_major_mode_by_name(ed, buffer, "ecex-mode"); +} + +static int cmd_ecex_api_completion_mode(ecex_t *ed) { + ecex_plugin_t *plugin = ecex_plugin_find(ed, ECEX_API_PLUGIN_ID); + if (!plugin) return -1; + + int enabled = !ecex_api_enabled(plugin); + ecex_plugin_slot_i32_set_scalar(plugin, ECEX_API_SLOT_ENABLED, enabled); + if (enabled) { + if (ecex_api_register_providers(ed) != 0) return -1; + } else { + ecex_api_remove_providers(ed); + } + ecex_message(ed, enabled ? "ecex API completion enabled" : "ecex API completion disabled"); + return 0; +} + +ECEX_PLUGIN_BEGIN(ecex_api_completion_plugin, ECEX_API_PLUGIN_ID) + if (!ecex_define_major_mode(ed, "ecex-mode")) return -1; + + if (ecex_plugin_slot_i32_get_scalar(plugin, ECEX_API_SLOT_ENABLED, -1) < 0) { + ecex_plugin_slot_i32_set_scalar(plugin, ECEX_API_SLOT_ENABLED, 1); + } + + ECEX_CONFIG_COMMAND("ecex-mode", cmd_ecex_mode); + ECEX_CONFIG_COMMAND("ecex-mode-complete", cmd_ecex_api_complete); + ECEX_CONFIG_COMMAND("ecex-mode-completion-mode", cmd_ecex_api_completion_mode); + ECEX_CONFIG_COMMAND("ecex-api-complete", cmd_ecex_api_complete); + ECEX_CONFIG_COMMAND("ecex-api-completion-mode", cmd_ecex_api_completion_mode); + if (ecex_api_enabled(plugin)) { + ECEX_CONFIG_TRY(ecex_api_register_providers(ed)); + } + return 0; +ECEX_PLUGIN_END + +#ifndef ECEX_NO_STANDALONE_CONFIG +ECEX_CONFIG_BEGIN + ECEX_CONFIG_INCLUDE(ecex_api_completion_plugin); +ECEX_CONFIG_END +#endif diff --git a/config/ecexrc.c b/config/ecexrc.c index 1d13f18..75f2470 100644 --- a/config/ecexrc.c +++ b/config/ecexrc.c @@ -5,6 +5,10 @@ #include "render_demo.c" #include "tetris.c" #include "markdown_plugin.c" +#include "which_key_plugin.c" +#include "c_mode_plugin.c" +#include "ecex_api_completion_plugin.c" +#include "c_tools_plugin.c" #undef ECEX_NO_STANDALONE_CONFIG #define RGB(r, g, b) ECEX_RGB8(r, g, b) @@ -141,11 +145,22 @@ ECEX_CONFIG_BEGIN ECEX_CONFIG_INCLUDE(ecex_render_demo_plugin); ECEX_CONFIG_INCLUDE(ecex_tetris_plugin); ECEX_CONFIG_INCLUDE(ecex_markdown_plugin); + ECEX_CONFIG_INCLUDE(ecex_which_key_plugin); + ECEX_CONFIG_INCLUDE(ecex_c_mode_plugin); + ECEX_CONFIG_INCLUDE(ecex_api_completion_plugin); + if (ecex_plugin_require_dependency(ed, "c-tools", "clang") == 0 && + ecex_plugin_require_dependency(ed, "c-tools", "clangd") == 0) { + ECEX_CONFIG_INCLUDE(ecex_c_tools_plugin); + } buffer_t *scratch = ecex_current_buffer(ed); buffer_insert(scratch, "ecex config loaded !\n"); - buffer_insert(scratch, "Try F1, C-x C-f, C-x C-s, C-x d file browser, F2 commands, F3 buffers, F4 demo menu, C-x b.\n"); + buffer_insert(scratch, "Try M-x, C-x C-f, C-x C-s, C-x d file browser, F2 commands, F3 buffers, F4 demo menu, C-x b.\n"); + buffer_insert(scratch, "which-key is enabled: press C-x or M-g and the minibuffer will show continuations.\n"); + buffer_insert(scratch, "TAB indents; in C buffers it smart-indents. C-TAB completes and cycles candidates.\n"); + buffer_insert(scratch, "C completion asks clangd; c-mode highlights C syntax.\n"); + buffer_insert(scratch, "When c-tools is loaded, C-x c l checks, C-x c k lints, C-x c e lints with ./include.\n"); buffer_insert(scratch, "Run M-x render-demo for a custom renderer demo, or M-x tetris for Tetris.\n"); buffer_insert(scratch, "Example line to edit then C-x C-e: ecex_set_font_size(ed, 28.0f);\n"); buffer_insert(scratch, "Example relative change: ecex_adjust_font_size(ed, 2.0f);\n\n"); diff --git a/config/markdown_plugin.c b/config/markdown_plugin.c index ded0e1c..1026d31 100644 --- a/config/markdown_plugin.c +++ b/config/markdown_plugin.c @@ -3,9 +3,28 @@ #define MD_TEXT_TITLE 1 typedef struct md_state { - ecex_t *ed; + ecex_plugin_t *plugin; } md_state_t; +static int md_line_is_fence(const char *line) { + const char *p = line; + if (!p) return 0; + while (*p == ' ' || *p == '\t') ++p; + return p[0] == '`' && p[1] == '`' && p[2] == '`'; +} + +static int md_in_code_at_line(buffer_t *buffer, int line) { + char scratch[256]; + int in_code = 0; + int i; + if (!buffer || line <= 0) return 0; + for (i = 0; i < line; ++i) { + if (ecex_buffer_line_copy_text(buffer, i, scratch, (int)sizeof(scratch)) < 0) continue; + if (md_line_is_fence(scratch)) in_code = !in_code; + } + return in_code; +} + static int md_render(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, void *userdata) { md_state_t *state = (md_state_t *)userdata; int draw_count; @@ -19,31 +38,32 @@ static int md_render(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, voi if (!ed || !buffer || !ctx || !state) return 0; - draw_count = ecex_var_i32(ed, state, "draw_count", 0) + 1; - ecex_var_i32_set_scalar(ed, state, "draw_count", draw_count); + draw_count = ecex_plugin_slot_i32_get_scalar(state->plugin, "draw_count", 0) + 1; + ecex_plugin_slot_i32_set_scalar(state->plugin, "draw_count", draw_count); if (draw_count <= 2 || (draw_count % 120) == 0) { ecex_log_int("markdown_plugin_draw: count=", draw_count); } - line_count = ecex_buffer_line_count_i(buffer); + line_count = ecex_buffer_line_count_int(buffer); ecex_log_int("markdown_plugin_draw: line_count=", line_count); if (line_count < 0) line_count = 0; - ecex_text_set_buffer_title(ed, state, MD_TEXT_TITLE, buffer); - ecex_draw_markdown_canvas_auto_i(ctx, state, MD_TEXT_TITLE); + ecex_plugin_text_set_from_buffer_title(state->plugin, MD_TEXT_TITLE, buffer); + ecex_draw_markdown_canvas_auto_i(ctx, state->plugin, MD_TEXT_TITLE); - y = ecex_markdown_body_y_i(ctx); - h = ecex_draw_context_height_i(ctx); - line_h = ecex_draw_context_line_height_i(ctx); - scroll = ecex_buffer_scroll_line(buffer); + y = ecex_markdown_body_y_px(ctx); + h = ecex_draw_context_height_px(ctx); + line_h = ecex_draw_context_line_height_px(ctx); + scroll = ecex_buffer_scroll_line_index(buffer); if (scroll < 0) scroll = 0; if (scroll > line_count) scroll = line_count; + in_code = md_in_code_at_line(buffer, scroll); for (i = scroll; i < line_count && y < h - line_h; ++i) { int packed; int advance; if (i < scroll + 4) ecex_log_int("markdown_plugin_draw: host line=", i); - packed = ecex_markdown_draw_line_from_buffer_i(ctx, state, buffer, i, y, in_code); + packed = ecex_markdown_draw_buffer_line_i(ctx, state->plugin, buffer, i, y, in_code); in_code = (packed & 0x10000) ? 1 : 0; advance = packed & 0xffff; if (advance <= 0) advance = line_h; @@ -55,12 +75,9 @@ static int md_render(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, voi static void md_free_state(void *userdata) { md_state_t *state = (md_state_t *)userdata; - ecex_t *ed; if (!state) return; - ed = state->ed; - ecex_text_free_owner(ed, state); - ecex_var_free_owner(ed, state); - ecex_object_free(ed, state); + ecex_plugin_text_free_all(state->plugin); + ecex_plugin_object_free(state->plugin, state); } static int md_view_buffer(ecex_t *ed, buffer_t *buffer) { @@ -68,12 +85,13 @@ static int md_view_buffer(ecex_t *ed, buffer_t *buffer) { if (!ed || !buffer) return -1; if (ecex_buffer_has_renderer(buffer)) ecex_buffer_clear_renderer(buffer); - state = (md_state_t *)ecex_object_calloc(ed, 1, sizeof(*state)); + ecex_plugin_t *plugin = ecex_plugin_find(ed, "markdown"); + state = (md_state_t *)ecex_plugin_object_calloc(plugin, "state", 1, sizeof(*state)); if (!state) return -1; - state->ed = ed; + state->plugin = plugin; if (ecex_buffer_set_renderer(buffer, md_render, state, md_free_state, ECEX_RENDER_REPLACE_CONTENT) != 0) { - ecex_object_free(ed, state); + ecex_plugin_object_free(plugin, state); return -1; } ecex_buffer_set_major_mode_by_name(ed, buffer, "markdown-mode"); @@ -105,19 +123,19 @@ static int cmd_markdown_toggle(ecex_t *ed) { return md_view_buffer(ed, buffer); } -int ecex_markdown_plugin(ecex_t *ed) { +ECEX_PLUGIN_BEGIN(ecex_markdown_plugin, "markdown") ECEX_CONFIG_MODE("markdown-mode"); ECEX_CONFIG_COMMAND("markdown-view", cmd_markdown_view); ECEX_CONFIG_COMMAND("markdown-source", cmd_markdown_source); ECEX_CONFIG_COMMAND("markdown-toggle", cmd_markdown_toggle); ECEX_CONFIG_MODE_BIND("markdown-mode", "C-c C-c", "markdown-toggle"); ECEX_CONFIG_MODE_BIND("markdown-mode", "C-c C-s", "markdown-source"); - ecex_register_file_handler(ed, ".md", md_file_handler); - ecex_register_file_handler(ed, ".markdown", md_file_handler); - ecex_register_file_handler(ed, ".mdown", md_file_handler); - ecex_register_file_handler(ed, ".mkd", md_file_handler); + ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".md", md_file_handler)); + ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".markdown", md_file_handler)); + ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".mdown", md_file_handler)); + ECEX_CONFIG_TRY(ecex_plugin_file_handler_register(plugin, ".mkd", md_file_handler)); return 0; -} +ECEX_PLUGIN_END #ifndef ECEX_NO_STANDALONE_CONFIG ECEX_CONFIG_BEGIN diff --git a/config/render_demo.c b/config/render_demo.c index 761b552..a0fb124 100644 --- a/config/render_demo.c +++ b/config/render_demo.c @@ -22,34 +22,33 @@ #define RENDER_DEMO_LABEL_CLICK 25 typedef struct render_demo_state { - ecex_t *ed; + ecex_plugin_t *plugin; int log_draw_count; } render_demo_state_t; -static int render_demo_get(ecex_t *ed, render_demo_state_t *s, const char *name, int fallback) { - if (!ed || !s || !name) return fallback; - return ecex_var_i32(ed, s, name, fallback); +static int render_demo_get(render_demo_state_t *s, const char *name, int fallback) { + if (!s || !name) return fallback; + return ecex_plugin_slot_i32_get_scalar(s->plugin, name, fallback); } -static void render_demo_set(ecex_t *ed, render_demo_state_t *s, const char *name, int value) { - if (!ed || !s || !name) return; - ecex_var_i32_set_scalar(ed, s, name, value); +static void render_demo_set(render_demo_state_t *s, const char *name, int value) { + if (!s || !name) return; + ecex_plugin_slot_i32_set_scalar(s->plugin, name, value); } -static void render_demo_reset(ecex_t *ed, render_demo_state_t *s) { +static void render_demo_reset(render_demo_state_t *s) { ecex_log_ptr("render_demo_reset: state=", s); - if (!ed || !s) return; + if (!s) return; - /* Store mutable demo state in the host variable registry. This mirrors the - * Tetris plugin flow and avoids relying on CCDJIT struct-field writes for - * values that must survive renderer, animation, and mouse callbacks. + /* Store mutable demo state in plugin slots. This avoids relying on CCDJIT + * struct-field writes for values shared by renderer, animation, and mouse callbacks. * Values are fixed-point integers: 0..1000 represents the available travel * range inside the demo frame. */ - render_demo_set(ed, s, RENDER_DEMO_VAR_X, 0); - render_demo_set(ed, s, RENDER_DEMO_VAR_Y, 500); - render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, 0); - render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, 500); - render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 0); + render_demo_set(s, RENDER_DEMO_VAR_X, 0); + render_demo_set(s, RENDER_DEMO_VAR_Y, 500); + render_demo_set(s, RENDER_DEMO_VAR_TARGET_X, 0); + render_demo_set(s, RENDER_DEMO_VAR_TARGET_Y, 500); + render_demo_set(s, RENDER_DEMO_VAR_MOVING, 0); s->log_draw_count = 0; } @@ -93,27 +92,27 @@ static int render_demo_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *c if (cw < 1) cw = 1; if (ch < 1) ch = 1; - ecex_draw_color_rgba8(ctx, 26, 28, 36, 255); + ecex_draw_color_rgba8_i(ctx, 26, 28, 36, 255); ecex_draw_rect_i(ctx, 0, 0, w, h); - ecex_draw_color_rgba8(ctx, 48, 96, 180, 255); + ecex_draw_color_rgba8_i(ctx, 48, 96, 180, 255); ecex_draw_rect_i(ctx, cx, cy, (cw * 55) / 100, line_h * 2); - ecex_draw_color_rgba8(ctx, 242, 230, 191, 255); + ecex_draw_color_rgba8_i(ctx, 242, 230, 191, 255); ecex_draw_rect_outline_i(ctx, cx, cy, cw, ch, 2); ecex_draw_label_i(ctx, cx + 12, cy + 10, RENDER_DEMO_LABEL_TITLE); - ecex_draw_color_rgba8(ctx, 229, 89, 64, 255); + ecex_draw_color_rgba8_i(ctx, 229, 89, 64, 255); ecex_draw_line_i(ctx, cx, cy + ch, cx + cw, cy, 3); - ecex_draw_color_rgba8(ctx, 160, 230, 180, 255); + ecex_draw_color_rgba8_i(ctx, 160, 230, 180, 255); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 3, RENDER_DEMO_LABEL_SUBTITLE); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 4, RENDER_DEMO_LABEL_SAFE_DRAW); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 5, RENDER_DEMO_LABEL_ANIM); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 6, RENDER_DEMO_LABEL_CLICK); - x_milli = render_demo_get(ed, s, RENDER_DEMO_VAR_X, 0); - y_milli = render_demo_get(ed, s, RENDER_DEMO_VAR_Y, 500); + x_milli = render_demo_get(s, RENDER_DEMO_VAR_X, 0); + y_milli = render_demo_get(s, RENDER_DEMO_VAR_Y, 500); ecex_draw_stat_i(ctx, cx + 12, cy + line_h * 8, RENDER_DEMO_LABEL_POSITION, x_milli); box = line_h * 2; @@ -132,20 +131,20 @@ static int render_demo_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *c box_x = area_x + (travel * x_milli) / 1000; box_y = area_y + (travel_y * y_milli) / 1000; - render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_X, box_x); - render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_Y, box_y); - render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_SIZE, box); - render_demo_set(ed, s, RENDER_DEMO_VAR_TRAVEL_X, travel); - render_demo_set(ed, s, RENDER_DEMO_VAR_TRAVEL_Y, travel_y); - render_demo_set(ed, s, RENDER_DEMO_VAR_AREA_X, area_x); - render_demo_set(ed, s, RENDER_DEMO_VAR_AREA_Y, area_y); + render_demo_set(s, RENDER_DEMO_VAR_BOX_X, box_x); + render_demo_set(s, RENDER_DEMO_VAR_BOX_Y, box_y); + render_demo_set(s, RENDER_DEMO_VAR_BOX_SIZE, box); + render_demo_set(s, RENDER_DEMO_VAR_TRAVEL_X, travel); + render_demo_set(s, RENDER_DEMO_VAR_TRAVEL_Y, travel_y); + render_demo_set(s, RENDER_DEMO_VAR_AREA_X, area_x); + render_demo_set(s, RENDER_DEMO_VAR_AREA_Y, area_y); - ecex_draw_color_rgba8(ctx, 70, 76, 90, 255); + ecex_draw_color_rgba8_i(ctx, 70, 76, 90, 255); ecex_draw_rect_outline_i(ctx, area_x, area_y, travel + box, travel_y + box, 1); - ecex_draw_color_rgba8(ctx, 89, 242, 140, 255); + ecex_draw_color_rgba8_i(ctx, 89, 242, 140, 255); ecex_draw_rect_i(ctx, box_x, box_y, box, box); - ecex_draw_color_rgba8(ctx, 20, 24, 30, 255); + ecex_draw_color_rgba8_i(ctx, 20, 24, 30, 255); ecex_draw_rect_outline_i(ctx, box_x, box_y, box, box, 2); return 0; @@ -185,20 +184,20 @@ static int render_demo_tick(ecex_t *ed, buffer_t *buffer, int now_ms, void *user (void)buffer; (void)now_ms; if (!ed || !s) return 0; - if (!render_demo_get(ed, s, RENDER_DEMO_VAR_MOVING, 0)) return 0; + if (!render_demo_get(s, RENDER_DEMO_VAR_MOVING, 0)) return 0; - x = render_demo_get(ed, s, RENDER_DEMO_VAR_X, 0); - y = render_demo_get(ed, s, RENDER_DEMO_VAR_Y, 500); - target_x = render_demo_get(ed, s, RENDER_DEMO_VAR_TARGET_X, x); - target_y = render_demo_get(ed, s, RENDER_DEMO_VAR_TARGET_Y, y); + x = render_demo_get(s, RENDER_DEMO_VAR_X, 0); + y = render_demo_get(s, RENDER_DEMO_VAR_Y, 500); + target_x = render_demo_get(s, RENDER_DEMO_VAR_TARGET_X, x); + target_y = render_demo_get(s, RENDER_DEMO_VAR_TARGET_Y, y); next_x = render_demo_delta_step(x, target_x); next_y = render_demo_delta_step(y, target_y); - render_demo_set(ed, s, RENDER_DEMO_VAR_X, render_demo_clamp_milli(next_x)); - render_demo_set(ed, s, RENDER_DEMO_VAR_Y, render_demo_clamp_milli(next_y)); + render_demo_set(s, RENDER_DEMO_VAR_X, render_demo_clamp_milli(next_x)); + render_demo_set(s, RENDER_DEMO_VAR_Y, render_demo_clamp_milli(next_y)); if (next_x == target_x && next_y == target_y) { - render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 0); + render_demo_set(s, RENDER_DEMO_VAR_MOVING, 0); ecex_log("render_demo_tick: target reached"); } return 1; @@ -218,11 +217,11 @@ static int render_demo_mouse(ecex_t *ed, buffer_t *buffer, int event, int x, int if (!ed || !s || button != ECEX_MOUSE_BUTTON_LEFT) return 0; if (event != ECEX_MOUSE_PRESS) return 0; - box = render_demo_get(ed, s, RENDER_DEMO_VAR_BOX_SIZE, 0); - area_x = render_demo_get(ed, s, RENDER_DEMO_VAR_AREA_X, 0); - area_y = render_demo_get(ed, s, RENDER_DEMO_VAR_AREA_Y, 0); - travel_x = render_demo_get(ed, s, RENDER_DEMO_VAR_TRAVEL_X, 0); - travel_y = render_demo_get(ed, s, RENDER_DEMO_VAR_TRAVEL_Y, 0); + box = render_demo_get(s, RENDER_DEMO_VAR_BOX_SIZE, 0); + area_x = render_demo_get(s, RENDER_DEMO_VAR_AREA_X, 0); + area_y = render_demo_get(s, RENDER_DEMO_VAR_AREA_Y, 0); + travel_x = render_demo_get(s, RENDER_DEMO_VAR_TRAVEL_X, 0); + travel_y = render_demo_get(s, RENDER_DEMO_VAR_TRAVEL_Y, 0); if (box <= 0) return 0; if (x < area_x || x >= area_x + travel_x + box || @@ -232,12 +231,12 @@ static int render_demo_mouse(ecex_t *ed, buffer_t *buffer, int event, int x, int nx = x - area_x - box / 2; ny = y - area_y - box / 2; - if (travel_x <= 0) render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, 0); - else render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, render_demo_clamp_milli((nx * 1000) / travel_x)); - if (travel_y <= 0) render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, 0); - else render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, render_demo_clamp_milli((ny * 1000) / travel_y)); + if (travel_x <= 0) render_demo_set(s, RENDER_DEMO_VAR_TARGET_X, 0); + else render_demo_set(s, RENDER_DEMO_VAR_TARGET_X, render_demo_clamp_milli((nx * 1000) / travel_x)); + if (travel_y <= 0) render_demo_set(s, RENDER_DEMO_VAR_TARGET_Y, 0); + else render_demo_set(s, RENDER_DEMO_VAR_TARGET_Y, render_demo_clamp_milli((ny * 1000) / travel_y)); - render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 1); + render_demo_set(s, RENDER_DEMO_VAR_MOVING, 1); ecex_log("render_demo_mouse: target set"); return 1; } @@ -245,8 +244,7 @@ static int render_demo_mouse(ecex_t *ed, buffer_t *buffer, int event, int x, int static void render_demo_free_state(void *userdata) { render_demo_state_t *s = (render_demo_state_t *)userdata; if (!s) return; - ecex_var_free_owner(s->ed, s); - ecex_config_free(s); + ecex_plugin_object_free(s->plugin, s); } static int cmd_render_demo(ecex_t *ed) { @@ -262,10 +260,11 @@ static int cmd_render_demo(ecex_t *ed) { s = (render_demo_state_t *)ecex_buffer_renderer_userdata(buffer); if (!s) { - s = (render_demo_state_t *)ecex_config_calloc(1, sizeof(*s)); + ecex_plugin_t *plugin = ecex_plugin_find(ed, "render-demo"); + s = (render_demo_state_t *)ecex_plugin_object_calloc(plugin, "state", 1, sizeof(*s)); if (!s) return -1; - s->ed = ed; - render_demo_reset(ed, s); + s->plugin = plugin; + render_demo_reset(s); if (ecex_buffer_set_renderer(buffer, render_demo_draw, s, render_demo_free_state, ECEX_RENDER_REPLACE_CONTENT) != 0) { render_demo_free_state(s); @@ -290,10 +289,11 @@ static int cmd_render_demo(ecex_t *ed) { return ecex_switch_buffer(ed, RENDER_DEMO_BUF); } -int ecex_render_demo_plugin(ecex_t *ed) { +ECEX_PLUGIN_BEGIN(ecex_render_demo_plugin, "render-demo") + (void)plugin; ECEX_CONFIG_COMMAND("render-demo", cmd_render_demo); return 0; -} +ECEX_PLUGIN_END #ifndef ECEX_NO_STANDALONE_CONFIG ECEX_CONFIG_BEGIN diff --git a/config/tetris.c b/config/tetris.c index 542963a..e86ab75 100644 --- a/config/tetris.c +++ b/config/tetris.c @@ -9,8 +9,7 @@ #define TETRIS_VAR_NEXT "next_piece" typedef struct tetris_state { - ecex_t *ed; - int *board; + ecex_plugin_t *plugin; int piece; int rot; int x; @@ -34,28 +33,61 @@ static int tetris_idx(int x, int y) { } static int tetris_shape_cell(int piece, int rot, int col, int row) { - return ecex_tetris_shape_cell(piece, rot, col, row); + int p = piece % 7; + int r = rot & 3; + if (p < 0) p += 7; + if (col < 0 || col >= 4 || row < 0 || row >= 4) return 0; + + if (p == 0) { + if ((r & 1) == 0) return row == 1; + return col == 1; + } + if (p == 1) return (row == 1 || row == 2) && (col == 1 || col == 2); + if (p == 2) { + if (r == 0) return (row == 1 && col >= 0 && col <= 2) || (row == 2 && col == 1); + if (r == 1) return (col == 1 && row >= 0 && row <= 2) || (row == 1 && col == 2); + if (r == 2) return (row == 1 && col >= 0 && col <= 2) || (row == 0 && col == 1); + return (col == 1 && row >= 0 && row <= 2) || (row == 1 && col == 0); + } + if (p == 3) { + if ((r & 1) == 0) return (row == 1 && (col == 1 || col == 2)) || (row == 2 && (col == 0 || col == 1)); + return (col == 1 && (row == 0 || row == 1)) || (col == 2 && (row == 1 || row == 2)); + } + if (p == 4) { + if ((r & 1) == 0) return (row == 1 && (col == 0 || col == 1)) || (row == 2 && (col == 1 || col == 2)); + return (col == 2 && (row == 0 || row == 1)) || (col == 1 && (row == 1 || row == 2)); + } + if (p == 5) { + if (r == 0) return (row == 1 && col >= 0 && col <= 2) || (row == 0 && col == 0); + if (r == 1) return (col == 1 && row >= 0 && row <= 2) || (row == 0 && col == 2); + if (r == 2) return (row == 1 && col >= 0 && col <= 2) || (row == 2 && col == 2); + return (col == 1 && row >= 0 && row <= 2) || (row == 2 && col == 0); + } + if (r == 0) return (row == 1 && col >= 0 && col <= 2) || (row == 0 && col == 2); + if (r == 1) return (col == 1 && row >= 0 && row <= 2) || (row == 2 && col == 2); + if (r == 2) return (row == 1 && col >= 0 && col <= 2) || (row == 2 && col == 0); + return (col == 1 && row >= 0 && row <= 2) || (row == 0 && col == 0); } -static int *tetris_board(ecex_t *ed, tetris_state_t *s) { - if (!ed || !s) return 0; - return (int *)ecex_var_get_or_alloc(ed, s, TETRIS_VAR_BOARD, (size_t)TETRIS_CELLS, sizeof(int)); +static int *tetris_board(tetris_state_t *s) { + if (!s) return 0; + return (int *)ecex_plugin_slot_alloc(s->plugin, TETRIS_VAR_BOARD, (size_t)TETRIS_CELLS, sizeof(int)); } -static int tetris_next_piece(ecex_t *ed, tetris_state_t *s) { - return ecex_var_i32(ed, s, TETRIS_VAR_NEXT, 0); +static int tetris_next_piece(tetris_state_t *s) { + return ecex_plugin_slot_i32_get_scalar(s ? s->plugin : 0, TETRIS_VAR_NEXT, 0); } -static void tetris_set_next_piece(ecex_t *ed, tetris_state_t *s, int piece) { +static void tetris_set_next_piece(tetris_state_t *s, int piece) { if (piece < 0) piece = 0; if (piece > 6) piece = piece % 7; - ecex_var_i32_set_scalar(ed, s, TETRIS_VAR_NEXT, piece); + ecex_plugin_slot_i32_set_scalar(s ? s->plugin : 0, TETRIS_VAR_NEXT, piece); } -static void tetris_clear_board(ecex_t *ed, tetris_state_t *s) { +static void tetris_clear_board(tetris_state_t *s) { int *board; if (!s) { ecex_log("tetris_clear_board: null state"); return; } - board = tetris_board(ed, s); + board = tetris_board(s); ecex_log_ptr("tetris_clear_board: board=", board); ecex_mem_zero(board, (size_t)TETRIS_CELLS * sizeof(int)); ecex_log("tetris_clear_board: done"); @@ -71,11 +103,11 @@ static int tetris_pick(tetris_state_t *s) { return piece; } -static int tetris_collides(ecex_t *ed, tetris_state_t *s, int piece, int rot, int px, int py) { +static int tetris_collides(tetris_state_t *s, int piece, int rot, int px, int py) { int r; int c; - int *board = tetris_board(ed, s); + int *board = tetris_board(s); if (!s || !board) return 1; for (r = 0; r < 4; ++r) { for (c = 0; c < 4; ++c) { @@ -93,44 +125,44 @@ static int tetris_collides(ecex_t *ed, tetris_state_t *s, int piece, int rot, in return 0; } -static void tetris_spawn(ecex_t *ed, tetris_state_t *s) { +static void tetris_spawn(tetris_state_t *s) { int queued; ecex_log_ptr("tetris_spawn: state=", s); if (!s) return; /* The sidebar must show the piece that will become active on the next - * spawn. Keep that queued value in the host variable registry, then consume + * spawn. Keep that queued value in a plugin slot, then consume * it here and immediately replace it with a freshly-picked preview. */ - queued = tetris_next_piece(ed, s); + queued = tetris_next_piece(s); if (queued < 0 || queued > 6) queued = tetris_pick(s); s->piece = queued; - tetris_set_next_piece(ed, s, tetris_pick(s)); + tetris_set_next_piece(s, tetris_pick(s)); ecex_log_int("tetris_spawn: current_piece=", s->piece); - ecex_log_int("tetris_spawn: preview_piece=", tetris_next_piece(ed, s)); + ecex_log_int("tetris_spawn: preview_piece=", tetris_next_piece(s)); s->rot = 0; s->x = 3; s->y = -1; s->last_drop_ms = 0; - if (tetris_collides(ed, s, s->piece, s->rot, s->x, s->y)) { + if (tetris_collides(s, s->piece, s->rot, s->x, s->y)) { ecex_log("tetris_spawn: immediate collision -> game over"); s->game_over = 1; } } -static void tetris_reset(ecex_t *ed, tetris_state_t *s) { +static void tetris_reset(tetris_state_t *s) { ecex_log_ptr("tetris_reset: state=", s); if (!s) return; - tetris_clear_board(ed, s); + tetris_clear_board(s); ecex_log("tetris_reset: after clear"); /* Avoid calling libc/time-returning host functions from CCDJIT plugin code. * Seed from stable host-owned addresses instead; good enough for a toy game * and much safer for the tiny JIT ABI. */ - s->rng = 0x9e3779b9u ^ (unsigned int)(size_t)s ^ (unsigned int)(size_t)tetris_board(ed, s); + s->rng = 0x9e3779b9u ^ (unsigned int)(size_t)s ^ (unsigned int)(size_t)tetris_board(s); ecex_log_int("tetris_reset: rng=", (int)s->rng); s->score = 0; s->lines = 0; @@ -139,15 +171,15 @@ static void tetris_reset(ecex_t *ed, tetris_state_t *s) { s->paused = 0; s->last_drop_ms = 0; s->drop_interval_ms = 650; - tetris_set_next_piece(ed, s, tetris_pick(s)); + tetris_set_next_piece(s, tetris_pick(s)); s->log_tick_count = 0; s->log_draw_count = 0; ecex_log("tetris_reset: spawning first piece"); - tetris_spawn(ed, s); + tetris_spawn(s); ecex_log("tetris_reset: done"); } -static void tetris_lock(ecex_t *ed, tetris_state_t *s) { +static void tetris_lock(tetris_state_t *s) { ecex_log_ptr("tetris_lock: state=", s); int r; int c; @@ -156,7 +188,7 @@ static void tetris_lock(ecex_t *ed, tetris_state_t *s) { int *board; if (!s) return; - board = tetris_board(ed, s); + board = tetris_board(s); if (!board) return; for (r = 0; r < 4; ++r) { @@ -214,7 +246,7 @@ static void tetris_lock(ecex_t *ed, tetris_state_t *s) { } ecex_log_int("tetris_lock: cleared=", cleared); - tetris_spawn(ed, s); + tetris_spawn(s); } @@ -244,11 +276,11 @@ static int tetris_top_out_if_hidden(tetris_state_t *s, const char *where) { return 1; } -static int tetris_soft_drop(ecex_t *ed, tetris_state_t *s) { +static int tetris_soft_drop(tetris_state_t *s) { if (!s) { ecex_log("tetris_soft_drop: null state"); return 0; } if (s->game_over || s->paused) return 0; - if (!tetris_collides(ed, s, s->piece, s->rot, s->x, s->y + 1)) { + if (!tetris_collides(s, s->piece, s->rot, s->x, s->y + 1)) { ++s->y; return 1; } @@ -256,12 +288,12 @@ static int tetris_soft_drop(ecex_t *ed, tetris_state_t *s) { ecex_log_int("tetris_soft_drop: locking piece=", s->piece); ecex_log_int("tetris_soft_drop: y=", s->y); if (tetris_top_out_if_hidden(s, "tetris_soft_drop: hidden lock -> game over")) return 1; - tetris_lock(ed, s); + tetris_lock(s); return 0; } -static int tetris_move_horizontal(ecex_t *ed, tetris_state_t *s, int dx) { +static int tetris_move_horizontal(tetris_state_t *s, int dx) { int old_x; int old_y; int old_rot; @@ -273,7 +305,7 @@ static int tetris_move_horizontal(ecex_t *ed, tetris_state_t *s, int dx) { old_y = s->y; old_rot = s->rot; - if (!tetris_collides(ed, s, s->piece, s->rot, old_x + dx, old_y)) { + if (!tetris_collides(s, s->piece, s->rot, old_x + dx, old_y)) { s->x = old_x + dx; /* Horizontal movement must never vertical-kick the active piece. * Keep these assignments explicit because plugin/JIT callback bugs are @@ -296,18 +328,18 @@ static int tetris_move_horizontal(ecex_t *ed, tetris_state_t *s, int dx) { return 0; } -static void tetris_hard_drop(ecex_t *ed, tetris_state_t *s) { +static void tetris_hard_drop(tetris_state_t *s) { int moved = 0; if (!s || s->game_over || s->paused) return; - while (!tetris_collides(ed, s, s->piece, s->rot, s->x, s->y + 1)) { + while (!tetris_collides(s, s->piece, s->rot, s->x, s->y + 1)) { ++s->y; ++moved; } s->score += moved * 2; if (tetris_top_out_if_hidden(s, "tetris_hard_drop: hidden lock -> game over")) return; - tetris_lock(ed, s); + tetris_lock(s); } static tetris_state_t *tetris_state_for_ed(ecex_t *ed) { @@ -330,14 +362,14 @@ static int tetris_alpha8(int alpha) { static void tetris_color(ecex_draw_context_t *ctx, int cell, int alpha) { alpha = tetris_alpha8(alpha); - if (cell == 1) ecex_draw_color_rgba8(ctx, 51, 191, 242, alpha); - else if (cell == 2) ecex_draw_color_rgba8(ctx, 242, 217, 51, alpha); - else if (cell == 3) ecex_draw_color_rgba8(ctx, 179, 89, 242, alpha); - else if (cell == 4) ecex_draw_color_rgba8(ctx, 77, 217, 89, alpha); - else if (cell == 5) ecex_draw_color_rgba8(ctx, 242, 64, 64, alpha); - else if (cell == 6) ecex_draw_color_rgba8(ctx, 64, 102, 242, alpha); - else if (cell == 7) ecex_draw_color_rgba8(ctx, 242, 140, 51, alpha); - else ecex_draw_color_rgba8(ctx, 41, 41, 46, alpha); + if (cell == 1) ecex_draw_color_rgba8_i(ctx, 51, 191, 242, alpha); + else if (cell == 2) ecex_draw_color_rgba8_i(ctx, 242, 217, 51, alpha); + else if (cell == 3) ecex_draw_color_rgba8_i(ctx, 179, 89, 242, alpha); + else if (cell == 4) ecex_draw_color_rgba8_i(ctx, 77, 217, 89, alpha); + else if (cell == 5) ecex_draw_color_rgba8_i(ctx, 242, 64, 64, alpha); + else if (cell == 6) ecex_draw_color_rgba8_i(ctx, 64, 102, 242, alpha); + else if (cell == 7) ecex_draw_color_rgba8_i(ctx, 242, 140, 51, alpha); + else ecex_draw_color_rgba8_i(ctx, 41, 41, 46, alpha); } static void tetris_draw_piece(ecex_draw_context_t *ctx, @@ -386,7 +418,7 @@ static int tetris_tick(ecex_t *ed, buffer_t *buffer, int now_ms, void *userdata) if (!s->game_over && !s->paused && now_ms - s->last_drop_ms >= s->drop_interval_ms) { ecex_log("tetris_tick: dropping piece"); - tetris_soft_drop(ed, s); + tetris_soft_drop(s); s->last_drop_ms = now_ms; return 1; } @@ -413,8 +445,8 @@ static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, v if (!s) { ecex_log("tetris_draw: null state"); return 0; } if (!ctx) { ecex_log("tetris_draw: null ctx"); return 0; } - board = tetris_board(ed, s); - if (!board) { ecex_log("tetris_draw: no registry board"); return 0; } + board = tetris_board(s); + if (!board) { ecex_log("tetris_draw: no plugin board slot"); return 0; } s->log_draw_count += 1; if (s->log_draw_count <= 8 || (s->log_draw_count % 60) == 0) { ecex_log_int("tetris_draw: count=", s->log_draw_count); @@ -423,7 +455,7 @@ static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, v } if (s->log_draw_count <= 3) ecex_log("tetris_draw: background"); - ecex_draw_color_rgba8(ctx, 20, 23, 28, 255); + ecex_draw_color_rgba8_i(ctx, 20, 23, 28, 255); ecex_draw_rect_i(ctx, 0, 0, (int)ctx->w, (int)ctx->h); max_board_w = ((int)ctx->content_w * 62) / 100; @@ -438,9 +470,9 @@ static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, v oy = (int)ctx->content_y + 12; if (s->log_draw_count <= 3) ecex_log("tetris_draw: board frame"); - ecex_draw_color_rgba8(ctx, 8, 9, 12, 255); + ecex_draw_color_rgba8_i(ctx, 8, 9, 12, 255); ecex_draw_rect_i(ctx, ox - 6, oy - 6, board_w + 12, board_h + 12); - ecex_draw_color_rgba8(ctx, 97, 102, 115, 255); + ecex_draw_color_rgba8_i(ctx, 97, 102, 115, 255); ecex_draw_rect_outline_i(ctx, ox - 6, oy - 6, board_w + 12, board_h + 12, 2); if (s->log_draw_count <= 3) ecex_log("tetris_draw: cells"); @@ -459,7 +491,7 @@ static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, v sy = oy; if (s->log_draw_count <= 3) ecex_log("tetris_draw: sidebar"); - ecex_draw_color_rgba8(ctx, 235, 235, 214, 255); + ecex_draw_color_rgba8_i(ctx, 235, 235, 214, 255); ecex_log("tetris_draw: sidebar title"); ecex_draw_label_i(ctx, sx, sy, 1); sy += ((int)ctx->line_height * 16) / 10; @@ -475,11 +507,11 @@ static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, v ecex_draw_label_i(ctx, sx, sy, 5); sy += ((int)ctx->line_height * 8) / 10; - ecex_log_int("tetris_draw: preview_piece=", tetris_next_piece(ed, s)); - ecex_draw_tetris_preview_i(ctx, tetris_next_piece(ed, s), sx, sy, (cell * 72) / 100, 255); + ecex_log_int("tetris_draw: preview_piece=", tetris_next_piece(s)); + ecex_draw_tetris_preview_i(ctx, tetris_next_piece(s), sx, sy, (cell * 72) / 100, 255); sy += cell * 4; - ecex_draw_color_rgba8(ctx, 184, 189, 199, 255); + ecex_draw_color_rgba8_i(ctx, 184, 189, 199, 255); ecex_log("tetris_draw: sidebar help"); ecex_draw_label_i(ctx, sx, sy, 6); sy += (int)ctx->line_height; ecex_draw_label_i(ctx, sx, sy, 7); sy += (int)ctx->line_height; @@ -489,9 +521,9 @@ static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, v ecex_draw_label_i(ctx, sx, sy, 11); if (s->paused || s->game_over) { - ecex_draw_color_rgba8(ctx, 0, 0, 0, 174); + ecex_draw_color_rgba8_i(ctx, 0, 0, 0, 174); ecex_draw_rect_i(ctx, ox, oy + (board_h * 43) / 100, board_w, ((int)ctx->line_height * 22) / 10); - ecex_draw_color_rgba8(ctx, 255, 235, 89, 255); + ecex_draw_color_rgba8_i(ctx, 255, 235, 89, 255); ecex_draw_label_i(ctx, ox + cell, oy + (board_h * 43) / 100 + ((int)ctx->line_height * 55) / 100, s->game_over ? 12 : 13); } @@ -502,8 +534,7 @@ static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, v static void tetris_free_state(void *userdata) { tetris_state_t *s = (tetris_state_t *)userdata; if (!s) return; - ecex_var_free_owner(s->ed, s); - ecex_config_free(s); + ecex_plugin_object_free(s->plugin, s); } static int cmd_tetris(ecex_t *ed) { @@ -527,25 +558,25 @@ static int cmd_tetris(ecex_t *ed) { ecex_log_ptr("cmd_tetris: existing state=", s); if (!s) { ecex_log_int("cmd_tetris: allocating state bytes=", (int)sizeof(*s)); - s = (tetris_state_t *)ecex_config_calloc(1, sizeof(*s)); + ecex_plugin_t *plugin = ecex_plugin_find(ed, "tetris"); + s = (tetris_state_t *)ecex_plugin_object_calloc(plugin, "state", 1, sizeof(*s)); ecex_log_ptr("cmd_tetris: allocated state=", s); if (!s) { ecex_log("cmd_tetris: allocation failed"); return -1; } - s->ed = ed; - ecex_log_int("cmd_tetris: registry board bytes=", (int)((size_t)TETRIS_CELLS * sizeof(int))); - ecex_log_ptr("cmd_tetris: registry board=", tetris_board(ed, s)); - if (!tetris_board(ed, s)) { - ecex_log("cmd_tetris: registry board allocation failed"); - ecex_var_free_owner(ed, s); - ecex_config_free(s); + s->plugin = plugin; + ecex_log_int("cmd_tetris: board slot bytes=", (int)((size_t)TETRIS_CELLS * sizeof(int))); + ecex_log_ptr("cmd_tetris: board slot=", tetris_board(s)); + if (!tetris_board(s)) { + ecex_log("cmd_tetris: board slot allocation failed"); + ecex_plugin_object_free(plugin, s); return -1; } - tetris_reset(ed, s); + tetris_reset(s); ecex_log("cmd_tetris: setting renderer"); if (ecex_buffer_set_renderer(buffer, tetris_draw, s, tetris_free_state, ECEX_RENDER_REPLACE_CONTENT) != 0) { ecex_log("cmd_tetris: set_renderer failed"); - ecex_config_free(s); + ecex_plugin_object_free(plugin, s); return -1; } @@ -577,19 +608,19 @@ static int cmd_tetris_new(ecex_t *ed) { ecex_log("cmd_tetris_new: enter"); tetris_state_t *s = tetris_state_for_ed(ed); if (!s) return cmd_tetris(ed); - tetris_reset(ed, s); + tetris_reset(s); return 0; } static int cmd_tetris_left(ecex_t *ed) { ecex_log("cmd_tetris_left: enter"); - tetris_move_horizontal(ed, tetris_state_for_ed(ed), -1); + tetris_move_horizontal(tetris_state_for_ed(ed), -1); return 0; } static int cmd_tetris_right(ecex_t *ed) { ecex_log("cmd_tetris_right: enter"); - tetris_move_horizontal(ed, tetris_state_for_ed(ed), 1); + tetris_move_horizontal(tetris_state_for_ed(ed), 1); return 0; } @@ -597,7 +628,7 @@ static int cmd_tetris_down(ecex_t *ed) { ecex_log("cmd_tetris_down: enter"); tetris_state_t *s = tetris_state_for_ed(ed); if (s) { - if (tetris_soft_drop(ed, s)) s->score += 1; + if (tetris_soft_drop(s)) s->score += 1; s->last_drop_ms = 0; } return 0; @@ -611,12 +642,12 @@ static int cmd_tetris_rotate(ecex_t *ed) { if (!s || s->game_over || s->paused) return 0; nr = (s->rot + 1) & 3; - if (!tetris_collides(ed, s, s->piece, nr, s->x, s->y)) { + if (!tetris_collides(s, s->piece, nr, s->x, s->y)) { s->rot = nr; - } else if (!tetris_collides(ed, s, s->piece, nr, s->x - 1, s->y)) { + } else if (!tetris_collides(s, s->piece, nr, s->x - 1, s->y)) { --s->x; s->rot = nr; - } else if (!tetris_collides(ed, s, s->piece, nr, s->x + 1, s->y)) { + } else if (!tetris_collides(s, s->piece, nr, s->x + 1, s->y)) { ++s->x; s->rot = nr; } @@ -626,7 +657,7 @@ static int cmd_tetris_rotate(ecex_t *ed) { static int cmd_tetris_drop(ecex_t *ed) { ecex_log("cmd_tetris_drop: enter"); - tetris_hard_drop(ed, tetris_state_for_ed(ed)); + tetris_hard_drop(tetris_state_for_ed(ed)); return 0; } @@ -645,8 +676,9 @@ static int cmd_tetris_quit(ecex_t *ed) { return ecex_execute_command(ed, "quit-window"); } -int ecex_tetris_plugin(ecex_t *ed) { +ECEX_PLUGIN_BEGIN(ecex_tetris_plugin, "tetris") ecex_log("ecex_tetris_plugin: enter"); + (void)plugin; ECEX_CONFIG_MODE(TETRIS_MODE); ECEX_CONFIG_COMMAND("tetris", cmd_tetris); ECEX_CONFIG_COMMAND("tetris-new", cmd_tetris_new); @@ -673,7 +705,7 @@ int ecex_tetris_plugin(ecex_t *ed) { ecex_log("ecex_tetris_plugin: registered all commands and keybinds"); return 0; -} +ECEX_PLUGIN_END #ifndef ECEX_NO_STANDALONE_CONFIG ECEX_CONFIG_BEGIN diff --git a/config/which_key_plugin.c b/config/which_key_plugin.c new file mode 100644 index 0000000..8d4fb50 --- /dev/null +++ b/config/which_key_plugin.c @@ -0,0 +1,107 @@ +#include "ecex.h" + +#define WK_HOOK_NAME "which-key" +#define WK_SLOT_ENABLED "enabled" + +typedef struct which_key_state { + ecex_plugin_t *plugin; + int showing; +} which_key_state_t; + +static int which_key_enabled(ecex_plugin_t *plugin) { + return ecex_plugin_slot_i32_get_scalar(plugin, WK_SLOT_ENABLED, 1) != 0; +} + +static int cmd_which_key_toggle(ecex_t *ed) { + ecex_plugin_t *plugin = ecex_plugin_find(ed, "which-key"); + if (!plugin) return -1; + + int enabled = !which_key_enabled(plugin); + ecex_plugin_slot_i32_set_scalar(plugin, WK_SLOT_ENABLED, enabled); + ecex_message(ed, enabled ? "which-key enabled" : "which-key disabled"); + return 0; +} + +static void which_key_prefix_hook(ecex_t *ed, const char *prefix, int event, void *userdata) { + which_key_state_t *state = (which_key_state_t *)userdata; + char line[1024]; + + if (!ed || !state || !state->plugin) return; + if (!which_key_enabled(state->plugin)) { + if (state->showing) ecex_message(ed, ""); + state->showing = 0; + return; + } + + if (event == ECEX_PREFIX_HOOK_CANCEL || + event == ECEX_PREFIX_HOOK_FINISH || + event == ECEX_PREFIX_HOOK_UNDEFINED) { + if (state->showing) ecex_message(ed, ""); + state->showing = 0; + return; + } + + if (event != ECEX_PREFIX_HOOK_BEGIN && event != ECEX_PREFIX_HOOK_UPDATE) { + return; + } + + if (ecex_describe_key_prefix(ed, ecex_current_buffer(ed), prefix, line, sizeof(line), 24) >= 0) { + ecex_message(ed, line); + state->showing = 1; + } +} + +static void which_key_command_hook(ecex_t *ed, + const char *command, + int event, + int result, + void *userdata) { + (void)command; + (void)result; + + which_key_state_t *state = (which_key_state_t *)userdata; + if (event == ECEX_COMMAND_HOOK_AFTER && state && state->showing) { + ecex_plugin_t *plugin = state->plugin ? state->plugin : ecex_plugin_find(ed, "which-key"); + if (plugin) { + ecex_message(ed, ""); + state->showing = 0; + } + } +} + +static void which_key_free(void *userdata) { + which_key_state_t *state = (which_key_state_t *)userdata; + if (!state) return; + ecex_plugin_object_free(state->plugin, state); +} + +ECEX_PLUGIN_BEGIN(ecex_which_key_plugin, "which-key") + if (ecex_plugin_slot_i32_get_scalar(plugin, WK_SLOT_ENABLED, -1) < 0) { + ecex_plugin_slot_i32_set_scalar(plugin, WK_SLOT_ENABLED, 1); + } + + ECEX_CONFIG_COMMAND("which-key-toggle", cmd_which_key_toggle); + + which_key_state_t *state = ecex_plugin_object_calloc(plugin, "state", 1, sizeof(*state)); + if (!state) return -1; + state->plugin = plugin; + + if (ecex_add_command_hook(ed, WK_HOOK_NAME, which_key_command_hook, state, 0) != 0) { + ecex_plugin_object_free(plugin, state); + return -1; + } + + if (ecex_add_prefix_hook(ed, WK_HOOK_NAME, which_key_prefix_hook, state, which_key_free) != 0) { + ecex_remove_command_hook(ed, WK_HOOK_NAME); + ecex_plugin_object_free(plugin, state); + return -1; + } + + return 0; +ECEX_PLUGIN_END + +#ifndef ECEX_NO_STANDALONE_CONFIG +ECEX_CONFIG_BEGIN + ECEX_CONFIG_INCLUDE(ecex_which_key_plugin); +ECEX_CONFIG_END +#endif |
