summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeo Tenenbaum <pommicket@gmail.com>2021-04-13 14:37:24 -0400
committerLeo Tenenbaum <pommicket@gmail.com>2021-04-13 14:37:24 -0400
commit77cfc38b57626e2c5a0b3549407c13acecb62d20 (patch)
tree2dd9bf9761ee6b2478ab39503995b35b72683f4e
parent69789c042460e012ec3054cc2e6ceeff25e394b9 (diff)
improve tag generation (don't just look at files in the cwd)
-rw-r--r--build.c204
-rw-r--r--command.c5
-rw-r--r--command.h2
-rw-r--r--config.c1
-rw-r--r--main.c2
-rw-r--r--tags.c104
-rw-r--r--ted.c6
-rw-r--r--ted.cfg4
-rw-r--r--ted.h2
-rw-r--r--ui.c8
-rw-r--r--util.c1
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