From 1c346f2aba30fcb581f20f2b67bd5e6adcb4a7e6 Mon Sep 17 00:00:00 2001 From: pommicket Date: Sat, 24 Dec 2022 00:28:50 -0500 Subject: find build directory in a much better way this will be useful for LSPs --- build.c | 39 ++++++++--------------------------- config.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ filesystem.h | 7 ++++++- main.c | 5 +++-- ted.c | 12 +++++++++++ ted.cfg | 10 +++++++++ ted.h | 2 ++ 7 files changed, 108 insertions(+), 34 deletions(-) diff --git a/build.c b/build.c index 3ac0001..07d57eb 100644 --- a/build.c +++ b/build.c @@ -78,47 +78,24 @@ static void build_start_with_command(Ted *ted, char const *command) { } static void build_start(Ted *ted) { - bool cargo = false, make = false; - - strbuf_cpy(ted->build_dir, ted->cwd); Settings *settings = ted_active_settings(ted); - char *command = settings->build_default_command; - - change_directory(ted->cwd); + char *root = ted_get_root_dir(ted); + strbuf_cpy(ted->build_dir, root); + change_directory(root); + free(root); + #if _WIN32 if (fs_file_exists("make.bat")) { command = "make.bat"; } else #endif - // check if Cargo.toml exists in this or the parent/parent's parent directory if (fs_file_exists("Cargo.toml")) { - cargo = true; - } else if (fs_file_exists(".." PATH_SEPARATOR_STR "Cargo.toml")) { - 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_path_full(ted, "../..", ted->build_dir, sizeof ted->build_dir); - cargo = true; - } else - // Check if Makefile exists in this or the parent/parent's parent directory - if (fs_file_exists("Makefile")) { - make = true; - } else if (fs_file_exists(".." PATH_SEPARATOR_STR "Makefile")) { - ted_path_full(ted, "..", ted->build_dir, sizeof ted->build_dir); - make = true; - } else if (fs_file_exists(".." PATH_SEPARATOR_STR ".." PATH_SEPARATOR_STR "Makefile")) { - ted_path_full(ted, "../..", ted->build_dir, sizeof ted->build_dir); - make = true; - } - - - // @TODO(eventually): `go build` - - if (cargo) { command = "cargo build"; - } else if (make) { + } else if (fs_file_exists("Makefile")) { command = "make -j12"; + } else if (fs_file_exists("go.mod")) { + command = "go build"; } build_start_with_command(ted, command); diff --git a/config.c b/config.c index 359f161..f3b6428 100644 --- a/config.c +++ b/config.c @@ -274,6 +274,7 @@ static OptionString const options_string[] = { {"build-default-command", options_zero.build_default_command, sizeof options_zero.build_default_command, true}, {"bg-shader", options_zero.bg_shader_text, sizeof options_zero.bg_shader_text, true}, {"bg-texture", options_zero.bg_shader_image, sizeof options_zero.bg_shader_image, true}, + {"root-identifiers", options_zero.root_identifiers, sizeof options_zero.root_identifiers, true}, }; static void option_bool_set(Settings *settings, const OptionBool *opt, bool value) { @@ -993,3 +994,69 @@ void config_free(Ted *ted) { ted->nstrings = 0; ted->default_settings = NULL; } + + +static char *last_separator(char *path) { + for (int i = (int)strlen(path) - 1; i >= 0; --i) + if (strchr(ALL_PATH_SEPARATORS, path[i])) + return &path[i]; + return NULL; +} + +// returns the best guess for the root directory of the project containing `path` (which should be an absolute path) +// the return value should be freed. +char *settings_get_root_dir(Settings *settings, const char *path) { + char best_path[TED_PATH_MAX]; + *best_path = '\0'; + u32 best_path_score = 0; + char pathbuf[TED_PATH_MAX]; + strbuf_cpy(pathbuf, path); + + while (1) { + FsDirectoryEntry **entries = fs_list_directory(pathbuf); + if (entries) { // note: this may actually be NULL on the first iteration if `path` is a file + for (int e = 0; entries[e]; ++e) { + const char *entry_name = entries[e]->name; + const char *ident_name = settings->root_identifiers; + while (*ident_name) { + const char *separators = ", \t\n\r\v"; + size_t ident_len = strcspn(ident_name, separators); + if (strlen(entry_name) == ident_len && strncmp(entry_name, ident_name, ident_len) == 0) { + // we found an identifier! + u32 score = U32_MAX - (u32)(ident_name - settings->root_identifiers); + if (score > best_path_score) { + best_path_score = score; + strbuf_cpy(best_path, pathbuf); + } + } + ident_name += ident_len; + ident_name += strspn(ident_name, separators); + } + } + fs_dir_entries_free(entries); + } + + char *p = last_separator(pathbuf); + if (!p) + break; + *p = '\0'; + if (!last_separator(pathbuf)) + break; // we made it all the way to / (or c:\ or whatever) + } + + if (*best_path) { + return str_dup(best_path); + } else { + // didn't find any identifiers. + // just return + // - `path` if it's a directory + // - the directory containing path if it's a file + if (fs_path_type(path) == FS_DIRECTORY) { + return str_dup(path); + } + strbuf_cpy(pathbuf, path); + char *sep = last_separator(pathbuf); + *sep = '\0'; + return str_dup(pathbuf); + } +} diff --git a/filesystem.h b/filesystem.h index be57323..0c18494 100644 --- a/filesystem.h +++ b/filesystem.h @@ -25,7 +25,7 @@ FsPermission fs_path_permission(char const *path); // Does this file exist? Returns false for directories. bool fs_file_exists(char const *path); // Returns a NULL-terminated array of the files/directories in this directory, or NULL if the directory does not exist/out of memory. -// When you're done with the entries, call free on each one, then on the array. +// When you're done with the entries, call fs_dir_entries_free (or call free on each entry, then on the whole array). // NOTE: The files/directories aren't returned in any particular order! FsDirectoryEntry **fs_list_directory(char const *dirname); // Create the directory specified by `path` @@ -41,6 +41,11 @@ int fs_mkdir(char const *path); // -1 if we can't get the cwd for whatever reason. int fs_get_cwd(char *buf, size_t buflen); +void fs_dir_entries_free(FsDirectoryEntry **entries) { + for (int i = 0; entries[i]; ++i) + free(entries[i]); + free(entries); +} #endif // FILESYSTEM_H_ diff --git a/main.c b/main.c index 997d3c9..a93439a 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,7 @@ /* @TODO: - LSP setting -- figure out workspace + - figure out workspace using root-files variable (also do this for :build) - make sure "save as" works - more LSP stuff: - go to definition using LSP @@ -9,13 +9,14 @@ - rename buffer->filename to buffer->path - make buffer->path NULL for untitled buffers & fix resulting mess - run everything through valgrind ideally with leak checking +- grep -i -n TODO *.[ch] - rust-analyzer bug reports: - bad json can give "Unexpected error: client exited without proper shutdown sequence" FUTURE FEATURES: - robust find (results shouldn't move around when you type things) - multiple files with command line arguments - configurable max buffer size + max view-only buffer size -- :set-build-command, don't let ../Cargo.toml override ./Makefile +- :set-build-command - add numlock as a key modifier? (but make sure "Ctrl+S" handles both "No NumLock+Ctrl+S" and "NumLock+Ctrl+S" - better undo chaining (dechain on backspace?) - allow multiple fonts (fonts directory?) diff --git a/ted.c b/ted.c index 8bc4f18..9000676 100644 --- a/ted.c +++ b/ted.c @@ -48,6 +48,18 @@ static void *ted_realloc(Ted *ted, void *p, size_t new_size) { return ret; } +// get the project root directory (based on the active buffer or ted->cwd if there's no active buffer). +// the return value should be freed +char *ted_get_root_dir(Ted *ted) { + Settings *settings = ted_active_settings(ted); + TextBuffer *buffer = ted->active_buffer; + if (buffer) { + return settings_get_root_dir(settings, buffer->filename); + } else { + return settings_get_root_dir(settings, ted->cwd); + } +} + Settings *ted_active_settings(Ted *ted) { if (ted->active_buffer) return buffer_settings(ted->active_buffer); diff --git a/ted.cfg b/ted.cfg index 98f8c5e..ef6f862 100644 --- a/ted.cfg +++ b/ted.cfg @@ -39,6 +39,16 @@ identifier-trigger-characters = off tags-max-depth = 2 # regenerate tags if an identifier is not found (with Ctrl+click)? regenerate-tags-if-not-found = yes +# this variable determines how ted finds the "root directory" of a project for +# running build commands and because LSPs need to know +# FOR EXAMPLE: If you have the file /a/b/c/d.txt open, +# ted will check each of the directories /, /a, /a/b, /a/b/c +# and set the root to whichever one has one of these files, +# breaking ties by order of appearance in the list below. +# So if /a/b/.git and /a/Makefile and /a/b/c/Cargo.toml exist, +# ted will select /a/b as the root. +# if no identifying files are found, the directory containing the current file is used. +root-identifiers = .ted-root.out, Cargo.toml, make.bat, CMakeLists.txt, Makefile, go.mod, .git # you can make your own custom background for ted using a shader. # an example is provided here. you will have access to the following variables: diff --git a/ted.h b/ted.h index a06b56d..637bed3 100644 --- a/ted.h +++ b/ted.h @@ -198,6 +198,7 @@ typedef struct { GlRcTexture *bg_texture; char bg_shader_text[4096]; char bg_shader_image[TED_PATH_MAX]; + char root_identifiers[4096]; char build_default_command[256]; // [i] = comma-separated string of file extensions for language i, or NULL for none char *language_extensions[LANG_COUNT]; @@ -538,3 +539,4 @@ static TextBuffer *find_search_buffer(Ted *ted); void config_read(Ted *ted, ConfigPart **parts, const char *filename); void config_parse(Ted *ted, ConfigPart **parts); void config_free(Ted *ted); +char *settings_get_root_dir(Settings *settings, const char *path); -- cgit v1.2.3