summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--command.c53
-rw-r--r--command.h7
-rw-r--r--config.c29
-rw-r--r--macro.c51
-rw-r--r--main.c4
-rw-r--r--menu.c12
-rw-r--r--ted.c3
-rw-r--r--ted.h36
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()
diff --git a/command.c b/command.c
index a384050..59166ec 100644
--- a/command.c
+++ b/command.c
@@ -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;
}
}
diff --git a/command.h b/command.h
index 9c91011..a17f39d 100644
--- a/command.h
+++ b/command.h
@@ -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;
diff --git a/config.c b/config.c
index c094218..c2f8230 100644
--- a/config.c
+++ b/config.c
@@ -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;
}
diff --git a/macro.c b/macro.c
new file mode 100644
index 0000000..81f1898
--- /dev/null
+++ b/macro.c
@@ -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);
+ }
+}
diff --git a/main.c b/main.c
index d3cdbed..75a40e3 100644
--- a/main.c
+++ b/main.c
@@ -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)
diff --git a/menu.c b/menu.c
index b894c35..5a65ea4 100644
--- a/menu.c
+++ b/menu.c
@@ -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);
}
diff --git a/ted.c b/ted.c
index 3c66703..865e9fe 100644
--- a/ted.c
+++ b/ted.c
@@ -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;
}
}
diff --git a/ted.h b/ted.h
index 2e935ff..7dc9602 100644
--- a/ted.h
+++ b/ted.h
@@ -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);