diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | buffer.c | 8 | ||||
-rw-r--r-- | build.c | 52 | ||||
-rw-r--r-- | command.c | 60 | ||||
-rw-r--r-- | command.h | 8 | ||||
-rw-r--r-- | config.c | 29 | ||||
-rw-r--r-- | control | 2 | ||||
-rw-r--r-- | macro.c | 65 | ||||
-rw-r--r-- | main.c | 48 | ||||
-rw-r--r-- | menu.c | 12 | ||||
-rw-r--r-- | syntax.c | 5 | ||||
-rw-r--r-- | ted.c | 3 | ||||
-rw-r--r-- | ted.cfg | 1 | ||||
-rw-r--r-- | ted.h | 46 |
14 files changed, 290 insertions, 51 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() @@ -308,6 +308,9 @@ bool buffer_indent_with_spaces(TextBuffer *buffer) { } String32 buffer_get_line(TextBuffer *buffer, u32 line_number) { + if (line_number >= buffer->nlines) { + return str32(NULL, 0); + } Line *line = &buffer->lines[line_number]; return (String32) { .str = line->str, .len = line->len @@ -1998,6 +2001,11 @@ void buffer_insert_text_at_cursor(TextBuffer *buffer, String32 str) { buffer_delete_selection(buffer); // delete any selected text BufferPos endpos = buffer_insert_text_at_pos(buffer, buffer->cursor_pos, str); buffer_cursor_move_to_pos(buffer, endpos); + if (str.len) { + // this actually isn't handled by the move above since buffer->cursor_pos + // should be moved to endpos anyways. might as well keep that there though + buffer_scroll_to_cursor(buffer); + } } void buffer_insert_char_at_cursor(TextBuffer *buffer, char32_t c) { @@ -118,8 +118,35 @@ static void build_go_to_error(Ted *ted) { if (ted_open_file(ted, error.path)) { TextBuffer *buffer = ted->active_buffer; assert(buffer); + + u32 index = error.column; + + if (error.columns_per_tab > 1) { + // get correct index + String32 line = buffer_get_line(buffer, error.line); + u32 column = 0; + index = 0; + while (column < error.column) { + if (index >= line.len) + break; + if (line.str[index] == '\t') { + column += error.columns_per_tab; + } else { + column += 1; + } + ++index; + } + } + + + BufferPos pos = { + .line = error.line, + .index = index, + }; + + // move cursor to error - buffer_cursor_move_to_pos(buffer, error.pos); + buffer_cursor_move_to_pos(buffer, pos); buffer->center_cursor_next_frame = true; // move cursor to error in build output @@ -180,16 +207,30 @@ void build_check_for_errors(Ted *ted) { continue; } bool is_error = true; + // well, for a bit of time i thought rust was weird + // and treated tabs as 4 columns + // apparently its just a bug, which ive filed here + // https://github.com/rust-lang/rust/issues/109537 + // we could treat ::: references as 4-columns-per-tab, + // but then that would be wrong if the bug gets fixed. + // all this is to say that columns_per_tab is currently always 1, + // but might be useful at some point. + u8 columns_per_tab = 1; char32_t *p = line->str, *end = p + line->len; { // rust errors look like: // " --> file:line:column" + // and can also include stuff like + // " ::: file:line:column" while (p != end && *p == ' ') { ++p; } - if (end - p >= 4 && p[0] == '-' && p[1] == '-' && p[2] == '>' && p[3] == ' ') { - p += 4; + if (end - p >= 4) { + String32 first4 = str32(p, 4); + if (str32_cmp_ascii(first4, "::: ") == 0 || str32_cmp_ascii(first4, "--> ") == 0) { + p += 4; + } } } @@ -243,9 +284,12 @@ void build_check_for_errors(Ted *ted) { pfilename += 3; path_full(ted->build_dir, pfilename, full_path, sizeof full_path); } + BuildError error = { .path = str_dup(full_path), - .pos = {.line = (u32)line_number, .index = (u32)column_number}, + .line = (u32)line_number, + .column = (u32)column_number, + .columns_per_tab = columns_per_tab, .build_output_line = line_idx }; arr_add(ted->build_errors, error); @@ -37,6 +37,7 @@ static CommandName command_names[] = { {"select-all", CMD_SELECT_ALL}, {"select-up-blank-line", CMD_SELECT_UP_BLANK_LINE}, {"select-down-blank-line", CMD_SELECT_DOWN_BLANK_LINE}, + {"clear-selection", CMD_CLEAR_SELECTION}, {"page-up", CMD_PAGE_UP}, {"page-down", CMD_PAGE_DOWN}, {"previous-position", CMD_PREVIOUS_POSITION}, @@ -96,6 +97,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 +139,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: @@ -194,6 +211,9 @@ void command_execute(Ted *ted, Command c, i64 argument) { if (buffer) buffer_select_left(buffer, argument); autocomplete_close(ted); break; + case CMD_CLEAR_SELECTION: + if (buffer) buffer_deselect(buffer); + break; case CMD_SELECT_RIGHT: if (buffer) buffer_select_right(buffer, argument); autocomplete_close(ted); @@ -267,11 +287,12 @@ void command_execute(Ted *ted, Command c, i64 argument) { autocomplete_close(ted); break; case CMD_PREVIOUS_POSITION: - buffer_cursor_move_to_prev_pos(buffer); + if (buffer) + buffer_cursor_move_to_prev_pos(buffer); 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); } @@ -597,7 +618,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); @@ -628,5 +649,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`. @@ -57,6 +54,7 @@ typedef enum { CMD_SELECT_PAGE_DOWN, CMD_SELECT_UP_BLANK_LINE, CMD_SELECT_DOWN_BLANK_LINE, + CMD_CLEAR_SELECTION, // insertion /// insert text @@ -147,6 +145,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; } @@ -1,5 +1,5 @@ Package: ted -Version: 2.1 +Version: 2.1r1 Section: text Priority: optional Architecture: amd64 @@ -0,0 +1,65 @@ +#include "ted.h" + +static void macro_clear(Macro *macro) { + arr_foreach_ptr(macro->actions, Action, act) { + free((char *)act->argument.string); + } + arr_free(macro->actions); + + memset(macro, 0, sizeof *macro); +} + +void macro_start_recording(Ted *ted, u32 index) { + if (index >= TED_MACRO_MAX) return; + if (ted->executing_macro) return; + if (ted->recording_macro) { + macro_stop_recording(ted); + return; + } + + command_execute(ted, CMD_CLEAR_SELECTION, 0); + + ted->recording_macro = &ted->macros[index]; + macro_clear(ted->recording_macro); +} + +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]; + macro_clear(macro); + } +} @@ -1,4 +1,5 @@ /* +- ctrl+9/0 to inc/dec number FUTURE FEATURES: - better undo chaining (dechain on backspace?) - font setting & support for multiple fonts to cover more characters @@ -16,11 +17,8 @@ FUTURE FEATURES: - TED_PLUGIN macro defined before including ted.h this can remove struct definitions to guarantee forwards compatibility - language dynamic registration -- keyboard macros - - 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) +- 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 +82,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" @@ -637,6 +636,9 @@ int main(int argc, char **argv) { } } break; case SDL_MOUSEBUTTONDOWN: { + if (ted->recording_macro) + break; // ignore mouse input during macros + Uint32 button = event.button.button; u8 times = event.button.clicks; // number of clicks float x = (float)event.button.x, y = (float)event.button.y; @@ -694,6 +696,9 @@ int main(int argc, char **argv) { } } break; case SDL_MOUSEBUTTONUP: { + if (ted->recording_macro) + break; // ignore mouse input during macros + Uint8 button = event.button.button; if (button < arr_count(ted->nmouse_releases)) { vec2 pos = Vec2((float)event.button.x, (float)event.button.y); @@ -703,6 +708,9 @@ int main(int argc, char **argv) { } } break; case SDL_MOUSEMOTION: { + if (ted->recording_macro) + break; // ignore mouse input during macros + float x = (float)event.motion.x, y = (float)event.motion.y; if (ted->drag_buffer != ted->active_buffer) ted->drag_buffer = NULL; @@ -726,7 +734,14 @@ int main(int argc, char **argv) { // unfortunately, some key combinations like ctrl+minus still register as a "-" text input event && (key_modifier & ~KEY_MODIFIER_SHIFT) == 0) { // insert the text - buffer_insert_utf8_at_cursor(buffer, text); + { + CommandContext ctx = {0}; + CommandArgument arg = { + .number = 0, + .string = text + }; + command_execute_ex(ted, CMD_INSERT_TEXT, arg, ctx); + } // check for trigger character LSP *lsp = buffer_lsp(buffer); Settings *settings = buffer_settings(buffer); @@ -1065,6 +1080,28 @@ int main(int argc, char **argv) { } } + if (ted->recording_macro) { + Font *font_bold = ted->font_bold; + u32 bg_color = ted_active_color(ted, COLOR_ERROR_BG); + u32 color = ted_active_color(ted, COLOR_TEXT); + u8 padding = ted_active_settings(ted)->padding; + const char *text = "Recording macro..."; + vec2 size = text_get_size_vec2(font_bold, text); + Rect r = { + .pos = vec2_sub(Vec2(window_width - 3 * padding, window_height - 3 * padding), size), + .size = vec2_add(size, Vec2(padding, padding)), + }; + gl_geometry_rect(r, bg_color); + Rect full_screen = { + .pos = {0, 0}, + .size = {window_width, window_height} + }; + gl_geometry_rect(full_screen, bg_color & 0xffffff0f); + text_utf8_anchored(font_bold, text, window_width - 2.5f * padding, window_height - 2.5f * padding, color, ANCHOR_BOTTOM_RIGHT); + gl_geometry_draw(); + text_render(font_bold); + } + ted_check_for_node_problems(ted); #if !NDEBUG @@ -1174,6 +1211,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); } @@ -1937,6 +1937,9 @@ void syntax_highlight(SyntaxState *state, Language lang, const char32_t *line, u static void syntax_highlight_c(SyntaxState *state, const char32_t *line, u32 line_len, SyntaxCharType *char_types) { syntax_highlight_c_cpp(state, line, line_len, char_types, LANG_C); } +static void syntax_highlight_glsl(SyntaxState *state, const char32_t *line, u32 line_len, SyntaxCharType *char_types) { + syntax_highlight_c_cpp(state, line, line_len, char_types, LANG_GLSL); +} static void syntax_highlight_cpp(SyntaxState *state, const char32_t *line, u32 line_len, SyntaxCharType *char_types) { syntax_highlight_c_cpp(state, line, line_len, char_types, LANG_CPP); } @@ -2066,7 +2069,7 @@ void syntax_register_builtin_languages(void) { .name = "GLSL", // not specified as of LSP 3.17, but this seems like the natural choice .lsp_identifier = "glsl", - .highlighter = syntax_highlight_html, + .highlighter = syntax_highlight_glsl, }, { .id = LANG_CSS, @@ -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; } } @@ -222,6 +222,7 @@ Ctrl+Shift+End = :select-end-of-file Ctrl+a = :select-all # go to previous cursor position Ctrl+p = :previous-position +# Ctrl+Shift+d = :clear-selection # insertion Tab = :tab @@ -28,7 +28,7 @@ extern "C" { #include "sdl-inc.h" /// Version number -#define TED_VERSION "2.1" +#define TED_VERSION "2.1r1" /// Version string #define TED_VERSION_FULL "ted v. " TED_VERSION /// Maximum path size ted handles. @@ -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 { @@ -531,7 +541,12 @@ typedef struct { typedef struct { char *path; - BufferPos pos; + u32 line; + u32 column; + /// if this is 1, then column == UTF-32 index. + /// if this is 4, for example, then column 4 in a line starting with a tab would + /// be the character right after the tab. + u8 columns_per_tab; /// which line in the build output corresponds to this error u32 build_output_line; } BuildError; @@ -695,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 @@ -702,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; @@ -914,6 +945,7 @@ u8 buffer_tab_width(TextBuffer *buffer); bool buffer_indent_with_spaces(TextBuffer *buffer); /// NOTE: this string will be invalidated when the line is edited!!! /// only use it briefly!! +/// returns an empty string if `line_number` is out of range. String32 buffer_get_line(TextBuffer *buffer, u32 line_number); /// get at most `nchars` characters starting from position `pos`. /// returns the number of characters actually available. @@ -1242,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. @@ -1461,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); |