diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | command.c | 53 | ||||
-rw-r--r-- | command.h | 7 | ||||
-rw-r--r-- | config.c | 29 | ||||
-rw-r--r-- | macro.c | 51 | ||||
-rw-r--r-- | main.c | 4 | ||||
-rw-r--r-- | menu.c | 12 | ||||
-rw-r--r-- | ted.c | 3 | ||||
-rw-r--r-- | ted.h | 36 |
9 files changed, 159 insertions, 38 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 15caed1..994f286 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(SOURCES buffer.c build.c colors.c command.c config.c find.c gl.c ide-autocomplete.c ide-definitions.c ide-highlights.c ide-hover.c ide-signature-help.c ide-usages.c lsp.c lsp-json.c lsp-parse.c lsp-write.c main.c menu.c node.c os.c session.c - stb_image.c stb_truetype.c syntax.c tags.c ted.c text.c ui.c util.c) + stb_image.c stb_truetype.c syntax.c tags.c ted.c text.c ui.c util.c macro.c) else() set(SOURCES main.c) endif() @@ -96,6 +96,9 @@ static CommandName command_names[] = { {"split-switch", CMD_SPLIT_SWITCH}, {"split-swap", CMD_SPLIT_SWAP}, {"escape", CMD_ESCAPE}, + {"macro-record", CMD_MACRO_RECORD}, + {"macro-stop", CMD_MACRO_STOP}, + {"macro-execute", CMD_MACRO_EXECUTE}, }; static_assert_if_possible(arr_count(command_names) == CMD_COUNT) @@ -135,23 +138,36 @@ const char *command_to_str(Command c) { return "???"; } -// get the string corresponding to this argument; returns NULL if it's not a string argument -static const char *arg_get_string(Ted *ted, i64 argument) { - if (argument < 0) return NULL; - if (argument & ARG_STRING) { - argument -= ARG_STRING; - if (argument < ted->nstrings) - return ted->strings[argument]; - } - return NULL; +void command_execute(Ted *ted, Command c, i64 argument) { + CommandArgument arg = { + .number = argument, + .string = NULL, + }; + command_execute_ex(ted, c, arg, (CommandContext){0}); } -void command_execute(Ted *ted, Command c, i64 argument) { +void command_execute_ex(Ted *ted, Command c, CommandArgument full_argument, CommandContext context) { TextBuffer *buffer = ted->active_buffer; Node *node = ted->active_node; Settings *settings = ted_active_settings(ted); - - + if (ted->recording_macro) + macro_add(ted, c, full_argument); + i64 argument = full_argument.number; + const char *argument_str = full_argument.string; + /* + it's important that when we're playing back a macro, + we only execute commands specifically from the macro. + for example, suppose the user opens the find menu and searches for "apple". + this might generate the macro: + open_find_menu() + insert_text("apple") + newline() + find_next("apple") // (generated by find.c) + if we ran these commands as-is, we'd end up searching for "apple" twice! + */ + if (ted->executing_macro && !context.from_macro) + return; + switch (c) { case CMD_UNKNOWN: case CMD_COUNT: @@ -272,7 +288,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { break; case CMD_INSERT_TEXT: { - const char *str = arg_get_string(ted, argument); + const char *str = argument_str; if (str) { buffer_insert_utf8_at_cursor(buffer, str); } @@ -598,7 +614,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { build_prev_error(ted); break; case CMD_SHELL: { - const char *str = arg_get_string(ted, argument); + const char *str = argument_str; if (str) { strbuf_cpy(ted->build_dir, ted->cwd); build_start_with_command(ted, str); @@ -629,5 +645,14 @@ void command_execute(Ted *ted, Command c, i64 argument) { case CMD_SPLIT_SWAP: if (node) node_split_swap(ted); break; + case CMD_MACRO_RECORD: + macro_start_recording(ted, (u32)argument); + break; + case CMD_MACRO_STOP: + macro_stop_recording(ted); + break; + case CMD_MACRO_EXECUTE: + macro_execute(ted, (u32)argument); + break; } } @@ -4,9 +4,6 @@ #ifndef COMMAND_H_ #define COMMAND_H_ -/// `i | ARG_STRING` when used as an argument refers to `ted->strings[i]` -#define ARG_STRING 0x4000000000000000 - /// command enum /// /// more documentation in `ted.cfg`. @@ -147,6 +144,10 @@ typedef enum { /// by default this is the escape key. closes menus, etc. CMD_ESCAPE, + + CMD_MACRO_RECORD, + CMD_MACRO_STOP, + CMD_MACRO_EXECUTE, CMD_COUNT } Command; @@ -610,7 +610,7 @@ static int config_part_qsort_cmp(const void *av, const void *bv) { return config_part_cmp(av, bv); } -static i64 config_read_string(Ted *ted, ConfigReader *cfg, char **ptext) { +static const char *config_read_string(Ted *ted, ConfigReader *cfg, char **ptext) { char *p; int backslashes = 0; u32 start_line = cfg->line_number; @@ -642,7 +642,7 @@ static i64 config_read_string(Ted *ted, ConfigReader *cfg, char **ptext) { config_err(cfg, "Unrecognized escape sequence: '\\%c'.", *p); *ptext += strlen(*ptext); arr_clear(str); - return -1; + return NULL; } break; case '\n': @@ -654,22 +654,21 @@ static i64 config_read_string(Ted *ted, ConfigReader *cfg, char **ptext) { config_err(cfg, "String doesn't end."); *ptext += strlen(*ptext); arr_clear(str); - return -1; + return NULL; } if (*p == delimiter) break; arr_add(str, *p); } - i64 str_idx = -1; + char *s = NULL; if (ted->nstrings < TED_MAX_STRINGS) { - char *s = strn_dup(str, arr_len(str)); - str_idx = ted->nstrings; + s = strn_dup(str, arr_len(str)); ted->strings[ted->nstrings++] = s; } arr_clear(str); *ptext = p + 1; - return str_idx; + return s; } static void settings_load_bg_shader(Ted *ted, Settings **applicable_settings, const char *bg_shader_text) { @@ -834,11 +833,14 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, KeyCombo key_combo = config_parse_key_combo(cfg, key); KeyAction action = {0}; action.key_combo = key_combo; - llong argument = 1; // default argument = 1 + CommandArgument argument = { + .number = 1, // default argument = 1 + .string = NULL + }; if (isdigit(*value)) { // read the argument char *endp; - argument = strtoll(value, &endp, 10); + argument.number = strtoll(value, &endp, 10); value = endp; } else if (*value == '"' || *value == '`') { // string argument @@ -846,7 +848,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, // restore newline to handle multi-line strings // a little bit hacky oh well *newline = '\n'; - argument = config_read_string(ted, cfg, &value); + argument.string = config_read_string(ted, cfg, &value); newline = strchr(value, '\n'); if (!newline) { @@ -950,7 +952,7 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, // a little bit hacky oh well *newline = '\n'; - i64 string = config_read_string(ted, cfg, &value); + const char *string = config_read_string(ted, cfg, &value); newline = strchr(value, '\n'); if (!newline) { @@ -960,9 +962,8 @@ static void config_parse_line(ConfigReader *cfg, Settings **applicable_settings, } *newline = '\0'; *pline = newline + 1; - if (string >= 0 && string < TED_MAX_STRINGS) { - value = ted->strings[string]; - } + if (string) + value = string; } @@ -0,0 +1,51 @@ +#include "ted.h" + +void macro_start_recording(Ted *ted, u32 index) { + if (index >= TED_MACRO_MAX) return; + if (ted->executing_macro) return; + ted->recording_macro = &ted->macros[index]; +} + +void macro_stop_recording(Ted *ted) { + ted->recording_macro = NULL; +} + +void macro_add(Ted *ted, Command command, CommandArgument argument) { + if (!ted->recording_macro) return; + if (command == CMD_MACRO_EXECUTE || command == CMD_MACRO_RECORD || command == CMD_MACRO_STOP) + return; + if (argument.string) + argument.string = str_dup(argument.string); + Action action = { + .command = command, + .argument = argument + }; + arr_add(ted->recording_macro->actions, action); +} + +void macro_execute(Ted *ted, u32 index) { + if (index >= TED_MACRO_MAX) return; + Macro *macro = &ted->macros[index]; + if (ted->recording_macro == macro) { + // don't allow running a macro while it's being recorded + return; + } + + ted->executing_macro = true; + CommandContext context = {0}; + context.from_macro = true; + arr_foreach_ptr(macro->actions, Action, act) { + command_execute_ex(ted, act->command, act->argument, context); + } + ted->executing_macro = false; +} + +void macros_free(Ted *ted) { + for (int i = 0; i < TED_MACRO_MAX; ++i) { + Macro *macro = &ted->macros[i]; + arr_foreach_ptr(macro->actions, Action, act) { + free((char *)act->argument.string); + } + arr_free(macro->actions); + } +} @@ -1,4 +1,5 @@ /* +- show "recording macro..." while recording macro FUTURE FEATURES: - better undo chaining (dechain on backspace?) - font setting & support for multiple fonts to cover more characters @@ -20,7 +21,6 @@ FUTURE FEATURES: - ctrl+9/0 to inc/dec number would be useful here - with macros we can really test performance of buffer_insert_text_at_pos, etc. (which should ideally be fast) - manual.md -- auto-reload config even for %included files - LSP request timeout BUG REPORTS IM TO LAZY TO FILE (RIGHT NOW) - rust-analyzer: @@ -84,6 +84,7 @@ BUG REPORTS IM TO LAZY TO FILE (RIGHT NOW) #include "ide-highlights.c" #include "ide-usages.c" #include "command.c" +#include "macro.c" #include "config.c" #include "session.c" #include "lsp.c" @@ -1174,6 +1175,7 @@ int main(int argc, char **argv) { text_font_free(ted->font); text_font_free(ted->font_bold); config_free(ted); + macros_free(ted); free(ted); #if _WIN32 for (int i = 0; i < argc; ++i) @@ -289,9 +289,15 @@ void menu_update(Ted *ted) { Command c = command_from_str(chosen_command); if (c != CMD_UNKNOWN) { char *argument = str32_to_utf8_cstr(buffer_get_line(&ted->argument_buffer, 0)), *endp = NULL; - long long arg = strtoll(argument, &endp, 0); - - if (*endp == '\0') { + long long arg = 1; + bool execute = true; + if (*argument) { + strtoll(argument, &endp, 0); + if (*endp != '\0') + execute = false; + } + + if (execute) { menu_close(ted); command_execute(ted, c, arg); } @@ -619,7 +619,8 @@ void ted_press_key(Ted *ted, SDL_Keycode keycode, SDL_Keymod modifier) { } else if (key_actions[mid].key_combo.value > key_combo.value) { hi = mid; } else { - command_execute(ted, key_actions[mid].command, key_actions[mid].argument); + CommandContext context = {0}; + command_execute_ex(ted, key_actions[mid].command, key_actions[mid].argument, context); return; } } @@ -181,13 +181,23 @@ typedef struct { /// extract key modifier from \ref KeyCombo #define KEY_COMBO_MODIFIER(combo) ((u32)((combo.value) & 0xff)) +typedef struct { + const char *string; + i64 number; +} CommandArgument; + +typedef struct { + bool from_macro; +} CommandContext; + /// Thing to do when a key combo is pressed. typedef struct { KeyCombo key_combo; Command command; - i64 argument; + CommandArgument argument; } KeyAction; + /// A SettingsContext is a context where a specific set of settings are applied. /// this corresponds to `[PATH//LANGUAGE.(section)]` in config files. typedef struct { @@ -700,6 +710,18 @@ typedef enum { MESSAGE_ERROR } MessageType; +typedef struct { + Command command; + CommandArgument argument; +} Action; + +typedef struct { + // dynamic array + Action *actions; +} Macro; + +#define TED_MACRO_MAX 256 + /// (almost) all data used by the ted application typedef struct Ted { /// all running LSP servers @@ -707,6 +729,10 @@ typedef struct Ted { /// current time (see time_get_seconds), as of the start of this frame double frame_time; + Macro macros[TED_MACRO_MAX]; + Macro *recording_macro; + bool executing_macro; + SDL_Window *window; Font *font_bold; Font *font; @@ -1248,6 +1274,7 @@ void command_init(void); Command command_from_str(const char *str); const char *command_to_str(Command c); void command_execute(Ted *ted, Command c, i64 argument); +void command_execute_ex(Ted *ted, Command c, CommandArgument argument, CommandContext context); // === config.c === /// first, we read all config files, then we parse them. @@ -1467,6 +1494,13 @@ void usages_find(Ted *ted); void usages_process_lsp_response(Ted *ted, const LSPResponse *response); void usages_frame(Ted *ted); +// === macro.c === +void macro_start_recording(Ted *ted, u32 index); +void macro_stop_recording(Ted *ted); +void macro_add(Ted *ted, Command command, CommandArgument argument); +void macro_execute(Ted *ted, u32 index); +void macros_free(Ted *ted); + // === menu.c === void menu_close(Ted *ted); void menu_open(Ted *ted, Menu menu); |