From 77cfc38b57626e2c5a0b3549407c13acecb62d20 Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Tue, 13 Apr 2021 14:37:24 -0400 Subject: improve tag generation (don't just look at files in the cwd) --- build.c | 204 ++++++++++++++++++++++++++++++++++++-------------------------- command.c | 5 +- command.h | 2 + config.c | 1 + main.c | 2 +- tags.c | 104 +++++++++++++++++++++++++++++--- ted.c | 6 +- ted.cfg | 4 +- ted.h | 2 + ui.c | 8 +-- util.c | 1 + 11 files changed, 233 insertions(+), 106 deletions(-) diff --git a/build.c b/build.c index 376566f..216c28e 100644 --- a/build.c +++ b/build.c @@ -1,46 +1,78 @@ -// clear build errors. -static void build_clear(Ted *ted) { +// clear build errors and stop +static void build_stop(Ted *ted) { + if (ted->building) + process_kill(&ted->build_process); + ted->building = false; + ted->build_shown = false; arr_foreach_ptr(ted->build_errors, BuildError, err) { free(err->filename); } arr_clear(ted->build_errors); + arr_foreach_ptr(ted->build_queue, char *, cmd) { + free(*cmd); + } + arr_clear(ted->build_queue); } -static void build_stop(Ted *ted) { - if (ted->building) - process_kill(&ted->build_process); - ted->building = false; - ted->build_shown = false; - build_clear(ted); +// call before adding anything to the build queue +static void build_queue_start(Ted *ted) { + build_stop(ted); } -// make sure you set ted->build_dir before running this! -static void build_start_with_command(Ted *ted, char const *command) { +// add a command to the build queue +static void build_queue_command(Ted *ted, char const *command) { + char *copy = str_dup(command); + if (copy) + arr_add(ted->build_queue, copy); +} + +// returns true if there are still commands left in the queue +static bool build_run_next_command_in_queue(Ted *ted) { + if (!ted->build_queue) + return false; assert(*ted->build_dir); change_directory(ted->build_dir); - if (ted->building) { - build_stop(ted); - } - build_clear(ted); // clear errors from previous build + char *command = ted->build_queue[0]; + arr_remove(ted->build_queue, 0); if (ted_save_all(ted)) { if (process_run(&ted->build_process, command)) { ted->building = true; ted->build_shown = true; TextBuffer *build_buffer = &ted->build_buffer; - // new empty build output buffer - buffer_new_file(build_buffer, NULL); - build_buffer->store_undo_events = false; // don't need undo events for build output buffer char32_t text[] = {'$', ' '}; buffer_insert_text_at_cursor(build_buffer, str32(text, 2)); buffer_insert_utf8_at_cursor(build_buffer, command); buffer_insert_char_at_cursor(build_buffer, '\n'); build_buffer->view_only = true; + free(command); + return true; } else { ted_seterr(ted, "Couldn't start build: %s", process_geterr(&ted->build_process)); + build_stop(ted); + return false; } + } else { + build_stop(ted); + return false; } } +// make sure you set ted->build_dir before running this! +static void build_queue_finish(Ted *ted) { + // new empty build output buffer + TextBuffer *build_buffer = &ted->build_buffer; + buffer_new_file(build_buffer, NULL); + build_buffer->store_undo_events = false; // don't need undo events for build output buffer + build_run_next_command_in_queue(ted); // run the first command +} + +// make sure you set ted->build_dir before running this! +static void build_start_with_command(Ted *ted, char const *command) { + build_queue_start(ted); + build_queue_command(ted, command); + build_queue_finish(ted); +} + static void build_start(Ted *ted) { bool cargo = false, make = false; @@ -59,17 +91,17 @@ static void build_start(Ted *ted) { if (fs_file_exists("Cargo.toml")) { cargo = true; } else if (fs_file_exists(".." PATH_SEPARATOR_STR "Cargo.toml")) { - ted_full_path(ted, "..", ted->build_dir, sizeof ted->build_dir); + ted_path_full(ted, "..", ted->build_dir, sizeof ted->build_dir); cargo = true; } else if (fs_file_exists(".." PATH_SEPARATOR_STR ".." PATH_SEPARATOR_STR "Cargo.toml")) { - ted_full_path(ted, "../..", ted->build_dir, sizeof ted->build_dir); + ted_path_full(ted, "../..", ted->build_dir, sizeof ted->build_dir); cargo = true; } else // Check if Makefile exists in this or the parent directory if (fs_file_exists("Makefile")) { make = true; } else if (fs_file_exists(".." PATH_SEPARATOR_STR "Makefile")) { - ted_full_path(ted, "..", ted->build_dir, sizeof ted->build_dir); + ted_path_full(ted, "..", ted->build_dir, sizeof ted->build_dir); make = true; } @@ -209,79 +241,81 @@ static void build_frame(Ted *ted, float x1, float y1, float x2, float y2) { // hasn't exited yet } else { buffer_insert_utf8_at_cursor(buffer, message); - ted->building = false; - - // check for errors - for (u32 line_idx = 0; line_idx < buffer->nlines; ++line_idx) { - Line *line = &buffer->lines[line_idx]; - if (line->len < 3) { - continue; - } - bool is_error = true; - char32_t *p = line->str, *end = p + line->len; - - { - // rust errors look like: - // " --> file:line:column" - while (p != end && *p == ' ') { - ++p; + buffer_insert_utf8_at_cursor(buffer, "\n"); + if (!build_run_next_command_in_queue(ted)) { + ted->building = false; + // done command queue; check for errors + for (u32 line_idx = 0; line_idx < buffer->nlines; ++line_idx) { + Line *line = &buffer->lines[line_idx]; + if (line->len < 3) { + continue; } - if (end - p >= 4 && p[0] == '-' && p[1] == '-' && p[2] == '>' && p[3] == ' ') { - p += 4; + bool is_error = true; + char32_t *p = line->str, *end = p + line->len; + + { + // rust errors look like: + // " --> file:line:column" + while (p != end && *p == ' ') { + ++p; + } + if (end - p >= 4 && p[0] == '-' && p[1] == '-' && p[2] == '>' && p[3] == ' ') { + p += 4; + } } - } - - // check if we have something like main.c:5 or main.c(5) - - // get file name - char32_t *filename_start = p; - while (p != end) { - if ((*p == ':' || *p == '(') - && p != line->str + 1) // don't catch "C:\thing\whatever.c" as "filename: C, line number: \thing\whatever.c" - break; - if (!is_source_path(*p)) { - is_error = false; - break; + + // check if we have something like main.c:5 or main.c(5) + + // get file name + char32_t *filename_start = p; + while (p != end) { + if ((*p == ':' || *p == '(') + && p != line->str + 1) // don't catch "C:\thing\whatever.c" as "filename: C, line number: \thing\whatever.c" + break; + if (!is_source_path(*p)) { + is_error = false; + break; + } + ++p; } - ++p; - } - if (p == end) is_error = false; - u32 filename_len = (u32)(p - filename_start); - if (filename_len == 0) is_error = false; - - if (is_error) { - ++p; // move past : or ( - int line_number = parse_nonnegative_integer(&p, end); - if (p != end && line_number > 0) { - // it's an error - line_number -= 1; // line numbers in output start from 1. - int column_number = 0; - // check if there's a column number - if (*p == ':') { - ++p; // move past : - int num = parse_nonnegative_integer(&p, end); - if (num > 0) { - column_number = num - 1; // column numbers in output start from 1 + if (p == end) is_error = false; + u32 filename_len = (u32)(p - filename_start); + if (filename_len == 0) is_error = false; + + if (is_error) { + ++p; // move past : or ( + int line_number = parse_nonnegative_integer(&p, end); + if (p != end && line_number > 0) { + // it's an error + line_number -= 1; // line numbers in output start from 1. + int column_number = 0; + // check if there's a column number + if (*p == ':') { + ++p; // move past : + int num = parse_nonnegative_integer(&p, end); + if (num > 0) { + column_number = num - 1; // column numbers in output start from 1 + } + } + char *filename = str32_to_utf8_cstr(str32(filename_start, filename_len)); + if (filename) { + char full_path[TED_PATH_MAX]; + path_full(ted->build_dir, filename, full_path, sizeof full_path); + BuildError error = { + .filename = str_dup(full_path), + .pos = {.line = (u32)line_number, .index = (u32)column_number}, + .build_output_line = line_idx + }; + arr_add(ted->build_errors, error); } - } - char *filename = str32_to_utf8_cstr(str32(filename_start, filename_len)); - if (filename) { - char full_path[TED_PATH_MAX]; - path_full(ted->build_dir, filename, full_path, sizeof full_path); - BuildError error = { - .filename = str_dup(full_path), - .pos = {.line = (u32)line_number, .index = (u32)column_number}, - .build_output_line = line_idx - }; - arr_add(ted->build_errors, error); } } } + + // go to the first error (if there is one) + ted->build_error = 0; + build_go_to_error(ted); } - - // go to the first error (if there is one) - ted->build_error = 0; - build_go_to_error(ted); } buffer->view_only = true; } diff --git a/command.c b/command.c index 29c76a8..7f54a68 100644 --- a/command.c +++ b/command.c @@ -363,7 +363,10 @@ void command_execute(Ted *ted, Command c, i64 argument) { menu_open(ted, MENU_SHELL); } } break; - + case CMD_GENERATE_TAGS: + tags_generate(ted); + break; + case CMD_GOTO_DEFINITION: menu_open(ted, MENU_GOTO_DEFINITION); break; diff --git a/command.h b/command.h index 49ffc09..80bd78b 100644 --- a/command.h +++ b/command.h @@ -80,6 +80,7 @@ ENUM_U16 { CMD_BUILD_PREV_ERROR, CMD_BUILD_NEXT_ERROR, CMD_SHELL, + CMD_GENERATE_TAGS, CMD_GOTO_DEFINITION, // "go to definition of..." CMD_GOTO_LINE, // open "goto line..." menu @@ -164,6 +165,7 @@ static CommandName const command_names[] = { {"build-prev-error", CMD_BUILD_PREV_ERROR}, {"build-next-error", CMD_BUILD_NEXT_ERROR}, {"shell", CMD_SHELL}, + {"generate-tags", CMD_GENERATE_TAGS}, {"goto-definition", CMD_GOTO_DEFINITION}, {"goto-line", CMD_GOTO_LINE}, {"split-horizontal", CMD_SPLIT_HORIZONTAL}, diff --git a/config.c b/config.c index 0583335..c70072d 100644 --- a/config.c +++ b/config.c @@ -191,6 +191,7 @@ void config_read(Ted *ted, char const *filename) { {"border-thickness", &settings->border_thickness, 1, 30}, {"padding", &settings->padding, 0, 100}, {"scrolloff", &settings->scrolloff, 1, 100}, + {"tags-max-depth", &settings->tags_max_depth, 1, 100}, }; OptionFloat const options_float[] = { {"cursor-blink-time-on", &settings->cursor_blink_time_on, 0, 1000}, diff --git a/main.c b/main.c index 1f8a13b..9cd2a86 100644 --- a/main.c +++ b/main.c @@ -67,8 +67,8 @@ bool tag_goto(Ted *ted, char const *tag); #include "ui.c" #include "find.c" #include "node.c" -#include "tags.c" #include "build.c" +#include "tags.c" #include "menu.c" #include "autocomplete.c" #include "command.c" diff --git a/tags.c b/tags.c index b6dc6ae..99d8a2c 100644 --- a/tags.c +++ b/tags.c @@ -1,15 +1,16 @@ -static char const *tags_filename(Ted *ted) { +static char const *tags_filename(Ted *ted, bool error_if_does_not_exist) { change_directory(ted->cwd); char const *filename = "tags"; - strbuf_printf(ted->tags_dir, "."); + ted_path_full(ted, ".", ted->tags_dir, sizeof ted->tags_dir); if (!fs_file_exists(filename)) { filename = "../tags"; - strbuf_printf(ted->tags_dir, ".."); + ted_path_full(ted, "..", ted->tags_dir, sizeof ted->tags_dir); if (!fs_file_exists(filename)) { filename = "../../tags"; - strbuf_printf(ted->tags_dir, "../.."); + ted_path_full(ted, "../..", ted->tags_dir, sizeof ted->tags_dir); if (!fs_file_exists(filename)) { - ted_seterr(ted, "No tags file. Try running ctags."); + if (error_if_does_not_exist) + ted_seterr(ted, "No tags file. Try running ctags."); filename = NULL; } } @@ -17,6 +18,89 @@ static char const *tags_filename(Ted *ted) { return filename; } +// is this a file we can generate tags for? +static bool is_source_file(char const *filename) { + char const *dot = strchr(filename, '.'); + char const *const extensions[] = { + "py", "c", "h", "cpp", "hpp", "cc", "hh", "cxx", "hxx", "C", "H", + "rb", "lua", "s", "asm", "js", "pl", "cs", "sh", "java", "php" + }; + if (!dot) return false; + for (size_t i = 0; i < arr_count(extensions); ++i) { + if (streq(dot + 1, extensions[i])) { + return true; + } + } + return false; +} + + +static void tags_generate_at_dir(Ted *ted, const char *dir, int depth) { + Settings const *settings = &ted->settings; + if (depth >= settings->tags_max_depth) { + return; + } + FsDirectoryEntry **entries = fs_list_directory(dir); + if (entries) { + char command[2048]; // 2048 is the limit on Windows XP, apparently + + #if __unix__ + // ctags.emacs's sorting depends on the locale + // (ctags-universal doesn't) + char const *cmd_prefix = "LC_ALL=C ctags --append"; + #else + char const *cmd_prefix = "ctags --append"; + #endif + bool any_files = false; + strcpy(command, cmd_prefix); + for (int i = 0; entries[i]; ++i) { + FsDirectoryEntry *entry = entries[i]; + char path[TED_PATH_MAX]; + path_full(dir, entry->name, path, sizeof path); + if (entry->name[0] != '.') { // ignore hidden directories and . and .. + switch (entry->type) { + case FS_FILE: { + if (is_source_file(entry->name)) { + size_t cmdlen = strlen(command), pathlen = strlen(path); + any_files = true; + // make sure command doesn't get too long + if (cmdlen + pathlen + 5 >= sizeof command) { + build_queue_command(ted, command); + strbuf_printf(command, "%s %s", cmd_prefix, path); + } else { + command[cmdlen++] = ' '; + memcpy(command + cmdlen, path, pathlen+1); + } + } + } break; + case FS_DIRECTORY: + tags_generate_at_dir(ted, path, depth+1); + break; + default: break; + } + } + } + if (any_files) { + build_queue_command(ted, command); + } + } +} + +// generate/re-generate tags. +static void tags_generate(Ted *ted) { + char const *filename = tags_filename(ted, false); + if (!filename) { + strcpy(ted->tags_dir, ted->cwd); + } + change_directory(ted->tags_dir); + strcpy(ted->build_dir, ted->tags_dir); + remove("tags"); // delete old tags file + build_queue_start(ted); + tags_generate_at_dir(ted, ted->tags_dir, 0); + build_queue_finish(ted); + change_directory(ted->cwd); +} + static int tag_try(FILE *fp, char const *tag) { if (ftell(fp) != 0) { while (1) { @@ -46,7 +130,7 @@ static int tag_try(FILE *fp, char const *tag) { // each element in out should be freed when you're done with them size_t tags_beginning_with(Ted *ted, char const *prefix, char **out, size_t out_size) { assert(out_size); - char const *tags_name = tags_filename(ted); + char const *tags_name = tags_filename(ted, true); if (!tags_name) return 0; FILE *file = fopen(tags_name, "rb"); if (!file) return 0; @@ -106,7 +190,7 @@ size_t tags_beginning_with(Ted *ted, char const *prefix, char **out, size_t out_ // returns true if the tag exists. bool tag_goto(Ted *ted, char const *tag) { - char const *tags_name = tags_filename(ted); + char const *tags_name = tags_filename(ted, true); if (!tags_name) return false; FILE *file = fopen(tags_name, "rb"); if (!file) return false; @@ -171,8 +255,8 @@ bool tag_goto(Ted *ted, char const *tag) { } assert(streq(name, tag)); char path[TED_PATH_MAX], full_path[TED_PATH_MAX]; - strbuf_printf(path, "%s/%s", ted->tags_dir, filename); - ted_full_path(ted, path, full_path, sizeof full_path); + path_full(ted->tags_dir, filename, path, sizeof path); + ted_path_full(ted, path, full_path, sizeof full_path); if (ted_open_file(ted, full_path)) { TextBuffer *buffer = ted->active_buffer; int line_number = atoi(address); @@ -243,7 +327,7 @@ bool tag_goto(Ted *ted, char const *tag) { static void tag_selector_open(Ted *ted) { // read tags file and extract tag names - char const *filename = tags_filename(ted); + char const *filename = tags_filename(ted, true); if (!filename) return; FILE *file = fopen(filename, "rb"); if (!file) return; diff --git a/ted.c b/ted.c index 91471b4..cb227fa 100644 --- a/ted.c +++ b/ted.c @@ -48,7 +48,7 @@ static void *ted_realloc(Ted *ted, void *p, size_t new_size) { return ret; } -static void ted_full_path(Ted *ted, char const *relpath, char *abspath, size_t abspath_size) { +static void ted_path_full(Ted *ted, char const *relpath, char *abspath, size_t abspath_size) { path_full(ted->cwd, relpath, abspath, abspath_size); } @@ -235,7 +235,7 @@ static Status ted_open_buffer(Ted *ted, u16 *buffer_idx, u16 *tab) { // Returns true on success static bool ted_open_file(Ted *ted, char const *filename) { char path[TED_PATH_MAX]; - ted_full_path(ted, filename, path, sizeof path); + ted_path_full(ted, filename, path, sizeof path); // first, check if file is already open bool *buffers_used = ted->buffers_used; @@ -273,7 +273,7 @@ static bool ted_new_file(Ted *ted, char const *filename) { u16 buffer_idx, tab_idx; char path[TED_PATH_MAX]; if (filename) - ted_full_path(ted, filename, path, sizeof path); + ted_path_full(ted, filename, path, sizeof path); else strbuf_cpy(path, TED_UNTITLED); if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { diff --git a/ted.cfg b/ted.cfg index d98f0b8..64a9230 100644 --- a/ted.cfg +++ b/ted.cfg @@ -28,6 +28,8 @@ build-default-command = make tags-filename = tags # restore previously opened files when ted is launched? restore-session = on +# search depth for files to generate tags for. if set to 0, tag generation/regeneration will do nothing +tags-max-depth = 2 [keyboard] # motion and selection @@ -128,7 +130,7 @@ Ctrl+[ = :build-prev-error Ctrl+] = :build-next-error Ctrl+! = :shell -Ctrl+t = "ctags *.*" :shell +Ctrl+t = :generate-tags Ctrl+d = :goto-definition Ctrl+g = :goto-line diff --git a/ted.h b/ted.h index a4bf0ca..90882cc 100644 --- a/ted.h +++ b/ted.h @@ -80,6 +80,7 @@ typedef struct { u8 border_thickness; u8 padding; u8 scrolloff; + u8 tags_max_depth; char build_default_command[256]; // [i] = comma-separated string of file extensions for language i, or NULL for none char *language_extensions[LANG_COUNT]; @@ -318,6 +319,7 @@ typedef struct Ted { // incomplete UTF-8 code point. This is where we store that "tail end" until more // data is available. (This is up to 3 bytes, null terminated) char build_incomplete_codepoint[4]; + char **build_queue; // allows execution of multiple commands -- needed for tags generation char warn_unsaved_names[TED_PATH_MAX]; // comma-separated list of files with unsaved changes (only applicable if warn_unsaved != 0) char warn_overwrite[TED_PATH_MAX]; // file name user is trying to overwrite char ask_reload[TED_PATH_MAX]; // file name which we want to reload diff --git a/ui.c b/ui.c index dc413c3..0b6a0a0 100644 --- a/ui.c +++ b/ui.c @@ -283,7 +283,7 @@ static Status file_selector_cd_(Ted *ted, FileSelector *fs, char const *path, in if (path[0] == PATH_SEPARATOR) { char new_cwd[TED_PATH_MAX]; // necessary because the full path of \ on windows isn't just \, it's c:\ or something - ted_full_path(ted, PATH_SEPARATOR_STR, new_cwd, sizeof new_cwd); + ted_path_full(ted, PATH_SEPARATOR_STR, new_cwd, sizeof new_cwd); strcpy(cwd, new_cwd); path += 1; } @@ -371,7 +371,7 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { if (option_chosen) { char path[TED_PATH_MAX]; - strbuf_printf(path, "%s%s%s", cwd, cwd[strlen(cwd)-1] == PATH_SEPARATOR ? "" : PATH_SEPARATOR_STR, option_chosen); + path_full(cwd, option_chosen, path, sizeof path); char *ret = NULL; switch (fs_path_type(path)) { @@ -451,9 +451,7 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { size_t path_size = strlen(name) + strlen(cwd) + 3; char *path = ted_calloc(ted, 1, path_size); if (path) { - snprintf(path, path_size - 1, "%s%s%s", cwd, - cwd[strlen(cwd) - 1] == PATH_SEPARATOR ? "" : PATH_SEPARATOR_STR, - name); + path_full(cwd, name, path, path_size); entries[i].path = path; } else { entries[i].path = NULL; // what can we do? diff --git a/util.c b/util.c index a0e6651..5c7fa4d 100644 --- a/util.c +++ b/util.c @@ -288,6 +288,7 @@ static void path_full(char const *dir, char const *relpath, char *abspath, size_ } else if (component_len == 2 && relpath[0] == '.' && relpath[1] == '.') { // .. char *lastsep = strrchr(abspath, PATH_SEPARATOR); + assert(lastsep); if (lastsep == abspath) lastsep[1] = '\0'; else -- cgit v1.2.3