From d2be40a282f845b1de700ef4be44c2316354fa5f Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Fri, 16 Jan 2026 08:58:13 +0100 Subject: [PATCH] plugin_directory_navigation --- src/text_editor/buffer.cpp | 18 +- src/text_editor/commands.cpp | 129 ++------------- src/text_editor/config.cpp | 93 ++++++++++- src/text_editor/plugin_command_window.cpp | 10 +- .../plugin_directory_navigation.cpp | 156 ++++++++++++++++++ src/text_editor/plugin_directory_navigation.h | 3 + .../plugin_search_open_buffers.cpp | 7 +- src/text_editor/text_editor.cpp | 2 + src/text_editor/text_editor.h | 2 + 9 files changed, 294 insertions(+), 126 deletions(-) create mode 100644 src/text_editor/plugin_directory_navigation.cpp create mode 100644 src/text_editor/plugin_directory_navigation.h diff --git a/src/text_editor/buffer.cpp b/src/text_editor/buffer.cpp index 0cb1bb2..7b5b989 100644 --- a/src/text_editor/buffer.cpp +++ b/src/text_editor/buffer.cpp @@ -306,6 +306,11 @@ API Int GetWordEnd(Buffer *buffer, Int pos) { return pos; } +bool IsOpenBoundary(char c) { + bool result = c == 0 || IsParen(c) || IsBrace(c) || c == ':' || c == '\t' || c == '\n' || c == '"' || c == '\''; + return result; +} + API bool IsLoadWord(char16_t w) { bool result = w == u'-' || w == u'/' || w == u'\\' || w == u':' || w == u'$' || w == u'_' || w == u'.' || w == u'!' || w == u'@'; if (!result) { @@ -1503,8 +1508,10 @@ Buffer *BufferOpenFile(String path) { buffer = CreateBuffer(sys_allocator, path); } else if (IsDir(path)) { buffer = CreateBuffer(sys_allocator, path); +#ifdef PLUGIN_DIRECTORY_NAVIGATION buffer->is_dir = true; buffer->temp = true; +#endif } else { String string = ReadFile(scratch, path); buffer = CreateBuffer(sys_allocator, path, string.len * 4 + 4096); @@ -1530,13 +1537,10 @@ bool BufferIsReferenced(BufferID buffer_id) { void ReopenBuffer(Buffer *buffer) { Scratch scratch; - if (buffer->is_dir) { - ResetBuffer(buffer); - for (FileIter it = IterateFiles(scratch, buffer->name); IsValid(it); Advance(&it)) { - RawAppendf(buffer, "%S\n", it.filename); - } - return; - } + +#ifdef PLUGIN_DIRECTORY_NAVIGATION + if (buffer->is_dir) { InsertDirectoryNavigation(buffer); return; } +#endif String string = ReadFile(scratch, buffer->name); if (string.len == 0) { diff --git a/src/text_editor/commands.cpp b/src/text_editor/commands.cpp index dfb5f89..104ce61 100644 --- a/src/text_editor/commands.cpp +++ b/src/text_editor/commands.cpp @@ -20,12 +20,13 @@ BSet GetConsoleSet() { } String GetDir(Buffer *buffer) { +#ifdef PLUGIN_DIRECTORY_NAVIGATION if (buffer->is_dir) { return buffer->name; - } else { - String result = ChopLastSlash(buffer->name); - return result; } +#endif + String result = ChopLastSlash(buffer->name); + return result; } String GetMainDir() { @@ -445,101 +446,6 @@ void CMD_GotoPrevInList() { GotoNextInList(main.window, -1); } RegisterCommand(CMD_GotoPrevInList, "alt-e", "For example: when jumping from build panel to build error, a jump point is setup, user can click this button to go over to the previous compiler error"); -bool IsOpenBoundary(char c) { - bool result = c == 0 || IsParen(c) || IsBrace(c) || c == ':' || c == '\t' || c == '\n' || c == '"' || c == '\''; - return result; -} - -#define ExpectP(x, ...) \ - if (!(x)) { \ - ReportErrorf("Failed to parse '" __FUNCTION__ "' command, " __VA_ARGS__); \ - return; \ - } - -void Set(String string) { - String name = SkipIdent(&string); - ExpectP(name.len != 0, "expected a variable name, instead got '%S'", string); - - Variable *var = NULL; - For (Variables) { - if (name == it.name) { - var = ⁢ - break; - } - } - - if (var) { - SkipWhitespace(&string); - if (var->type == VariableType_String) { - char c = At(string, 0); - ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string); - string = Skip(string, 1); - String quote = SkipUntil(&string, {&c, 1}); - ExpectP(At(string, 0) == c, ":Set %S , unclosed quote", name); - ReportConsolef(":Set %S %c%S%c", name, c, quote, c); - *var->string = Intern(&GlobalInternTable, quote); - } else if (var->type == VariableType_Int) { - ExpectP(IsDigit(At(string, 0)), "Expected an integer to follow the command name, instead got: %S", string); - Int number = SkipInt(&string); - ReportConsolef(":Set %S %lld", name, number); - *var->i = number; - } else if (var->type == VariableType_Float) { - ExpectP(IsDigit(At(string, 0)), "Expected float to follow the command name, instead got: %S", string); - Float number = SkipFloat(&string); - ReportConsolef(":Set %S %f", name, number); - *var->f = number; - } else if (var->type == VariableType_Color) { - ExpectP(IsHexDigit(At(string, 0)), "Expected hex integer to follow the command name, instead got: %S", string); - String begin = {string.data, 0}; - while (IsHexDigit(At(string, 0))) { - string = Skip(string, 1); - begin.len += 1; - } - ReportConsolef(":Set %S %S", name, begin); - var->color->value = (uint32_t)strtoll(begin.data, NULL, 16); - } ElseInvalidCodepath(); - - - if (name == "FontSize" || name == "PathToFont") { - ReloadFont(PathToFont, (U32)FontSize); - } - -#ifdef PLUGIN_PROJECT_MANAGEMENT - if (name == "ProjectDirectory") { - SetProjectDirectory(*var->string); - } -#endif - return; - } - - Command *cmd = NULL; - For (GlobalCommands) { - if (it.name == name) { - cmd = ⁢ - break; - } - } - - if (cmd) { - SkipWhitespace(&string); - char c = At(string, 0); - ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string); - string = Skip(string, 1); - String quote = SkipUntil(&string, {&c, 1}); - ExpectP(At(string, 0) == c, "Failed to parse command, unclose quote"); - quote = Intern(&GlobalInternTable, quote); - ReportConsolef(":Set %S %c%S%c", name, c, quote, c); - Trigger *trigger = ParseKeyCached(quote); - if (trigger) { - cmd->binding = quote; - cmd->trigger = trigger; - } - return; - } - - ReportErrorf("Failed to :Set, no such variable found: %S", name); -} - ResolvedOpen ResolveOpen(Allocator alo, String path, ResolveOpenMeta meta) { ResolvedOpen result = {}; path = Trim(path); @@ -692,18 +598,12 @@ BSet Open(Window *window, String path, ResolveOpenMeta meta, bool set_active = t if (set_active) { NextActiveWindowID = set.window->id; } + View *view = WindowOpenBufferView(set.window, o.path); if (IsDir(o.path)) { - View *view = WindowOpenBufferView(set.window, o.path); - SetFuzzy(view); - Buffer *buffer = GetBuffer(view->active_buffer); - ResetBuffer(buffer); - RawAppendf(buffer, "\n"); - for (FileIter it = IterateFiles(scratch, o.path); IsValid(it); Advance(&it)) { - RawAppendf(buffer, "%S\n", it.filename); - } - SelectRange(view, GetBufferBeginAsRange(buffer)); +#ifdef PLUGIN_DIRECTORY_NAVIGATION + OpenDirectoryNavigation(view); +#endif } else { - View *view = WindowOpenBufferView(set.window, o.path); Buffer *buffer = GetBuffer(view->active_buffer); if (o.line != -1) { if (o.col == -1) o.col = 1; @@ -1010,12 +910,6 @@ void CMD_Next() { NextActiveWindowID = main.window->id; } RegisterCommand(CMD_Next, "alt-shift-q | mousex2", "Go to next position, after backtracking, in the primary window"); -void CMD_OpenUpFolder() { - BSet main = GetBSet(PrimaryWindowID); - String name = ChopLastSlash(main.buffer->name); - Open(name); -} RegisterCommand(CMD_OpenUpFolder, "ctrl-o", "Open current's file directory or up directory in other cases"); - void CMD_MakeFontLarger() { FontSize += 1; ReloadFont(PathToFont, (U32)FontSize); @@ -1144,9 +1038,14 @@ void Coro_ReplaceAll(mco_coro *co) { } ForItem (buffer, Buffers) { - if (buffer->special || buffer->is_dir || buffer->temp || buffer->dont_try_to_save_in_bulk_ops) { + if (buffer->special || buffer->temp || buffer->dont_try_to_save_in_bulk_ops) { continue; } +#ifdef PLUGIN_DIRECTORY_NAVIGATION + if (buffer->is_dir) { + continue; + } +#endif View *view = OpenBufferView(buffer->name); if (SelectAllOccurences(view, needle)) { diff --git a/src/text_editor/config.cpp b/src/text_editor/config.cpp index 218b54f..fc41de0 100644 --- a/src/text_editor/config.cpp +++ b/src/text_editor/config.cpp @@ -350,4 +350,95 @@ BufferID LoadConfig(String config_path) { window->active_view = NullViewID; } return buffer->id; -} \ No newline at end of file +} + + +#define ExpectP(x, ...) \ + if (!(x)) { \ + ReportErrorf("Failed to parse '" __FUNCTION__ "' command, " __VA_ARGS__); \ + return; \ + } + +void Set(String string) { + String name = SkipIdent(&string); + ExpectP(name.len != 0, "expected a variable name, instead got '%S'", string); + + Variable *var = NULL; + For (Variables) { + if (name == it.name) { + var = ⁢ + break; + } + } + + if (var) { + SkipWhitespace(&string); + if (var->type == VariableType_String) { + char c = At(string, 0); + ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string); + string = Skip(string, 1); + String quote = SkipUntil(&string, {&c, 1}); + ExpectP(At(string, 0) == c, ":Set %S , unclosed quote", name); + ReportConsolef(":Set %S %c%S%c", name, c, quote, c); + *var->string = Intern(&GlobalInternTable, quote); + } else if (var->type == VariableType_Int) { + ExpectP(IsDigit(At(string, 0)), "Expected an integer to follow the command name, instead got: %S", string); + Int number = SkipInt(&string); + ReportConsolef(":Set %S %lld", name, number); + *var->i = number; + } else if (var->type == VariableType_Float) { + ExpectP(IsDigit(At(string, 0)), "Expected float to follow the command name, instead got: %S", string); + Float number = SkipFloat(&string); + ReportConsolef(":Set %S %f", name, number); + *var->f = number; + } else if (var->type == VariableType_Color) { + ExpectP(IsHexDigit(At(string, 0)), "Expected hex integer to follow the command name, instead got: %S", string); + String begin = {string.data, 0}; + while (IsHexDigit(At(string, 0))) { + string = Skip(string, 1); + begin.len += 1; + } + ReportConsolef(":Set %S %S", name, begin); + var->color->value = (uint32_t)strtoll(begin.data, NULL, 16); + } ElseInvalidCodepath(); + + + if (name == "FontSize" || name == "PathToFont") { + ReloadFont(PathToFont, (U32)FontSize); + } + +#ifdef PLUGIN_PROJECT_MANAGEMENT + if (name == "ProjectDirectory") { + SetProjectDirectory(*var->string); + } +#endif + return; + } + + Command *cmd = NULL; + For (GlobalCommands) { + if (it.name == name) { + cmd = ⁢ + break; + } + } + + if (cmd) { + SkipWhitespace(&string); + char c = At(string, 0); + ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string); + string = Skip(string, 1); + String quote = SkipUntil(&string, {&c, 1}); + ExpectP(At(string, 0) == c, "Failed to parse command, unclose quote"); + quote = Intern(&GlobalInternTable, quote); + ReportConsolef(":Set %S %c%S%c", name, c, quote, c); + Trigger *trigger = ParseKeyCached(quote); + if (trigger) { + cmd->binding = quote; + cmd->trigger = trigger; + } + return; + } + + ReportErrorf("Failed to :Set, no such variable found: %S", name); +} diff --git a/src/text_editor/plugin_command_window.cpp b/src/text_editor/plugin_command_window.cpp index df0ec3f..dc9f8e4 100644 --- a/src/text_editor/plugin_command_window.cpp +++ b/src/text_editor/plugin_command_window.cpp @@ -35,7 +35,10 @@ void CMD_ShowDebugBufferList() { NextActiveWindowID = command_bar.window->id; ResetBuffer(command_bar.buffer); For (Buffers) { - bool is_special = it->special || it->temp || it->is_dir || it->dont_try_to_save_in_bulk_ops; + bool is_special = it->special || it->temp || it->dont_try_to_save_in_bulk_ops; +#ifdef PLUGIN_DIRECTORY_NAVIGATION + is_special |= it->is_dir; +#endif if (!is_special) { continue; } @@ -56,7 +59,10 @@ void CMD_ShowBufferList() { NextActiveWindowID = command_bar.window->id; ResetBuffer(command_bar.buffer); For (Buffers) { - bool is_special = it->special || it->temp || it->is_dir || it->dont_try_to_save_in_bulk_ops; + bool is_special = it->special || it->temp || it->dont_try_to_save_in_bulk_ops; +#ifdef PLUGIN_DIRECTORY_NAVIGATION + is_special |= it->is_dir; +#endif if (is_special) { continue; } diff --git a/src/text_editor/plugin_directory_navigation.cpp b/src/text_editor/plugin_directory_navigation.cpp new file mode 100644 index 0000000..8db444b --- /dev/null +++ b/src/text_editor/plugin_directory_navigation.cpp @@ -0,0 +1,156 @@ +// @todo: On save rename files, delete files etc ... +// Instead of toying with Reopen maybe it should actually detect changes in directory etc. on update + +void CMD_OpenUpFolder() { + BSet main = GetBSet(PrimaryWindowID); + String name = ChopLastSlash(main.buffer->name); + Open(name); +} RegisterCommand(CMD_OpenUpFolder, "ctrl-o", "Open current's file directory or up directory in other cases"); + +void InsertDirectoryNavigation(Buffer *buffer) { + Assert(buffer->is_dir); + Scratch scratch; + ResetBuffer(buffer); + RawAppendf(buffer, "\n"); + for (FileIter it = IterateFiles(scratch, buffer->name); IsValid(it); Advance(&it)) { + RawAppendf(buffer, "%S\n", it.filename); + } +} + +void OpenDirectoryNavigation(View *view) { + SetFuzzy(view); + Buffer *buffer = GetBuffer(view->active_buffer); + InsertDirectoryNavigation(buffer); + SelectRange(view, GetBufferBeginAsRange(buffer)); +} + +#if 0 + +typedef struct { + HANDLE dir; + OVERLAPPED ov; + HANDLE event; + BYTE buffer[4096]; + BOOL pending; +} DirectoryWatcher; + +BOOL InitDirectoryWatcher(DirectoryWatcher *w, const wchar_t *path) +{ + ZeroMemory(w, sizeof(*w)); + + w->dir = CreateFileW( + path, + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL + ); + + if (w->dir == INVALID_HANDLE_VALUE) + return FALSE; + + w->event = CreateEventW(NULL, TRUE, FALSE, NULL); + if (!w->event) + return FALSE; + + w->ov.hEvent = w->event; + w->pending = FALSE; + return TRUE; +} + +BOOL WasDirectoryModified(DirectoryWatcher *w) +{ + DWORD bytes; + + if (!w->pending) { + ResetEvent(w->event); + w->pending = ReadDirectoryChangesW( + w->dir, + w->buffer, + sizeof(w->buffer), + TRUE, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE, + NULL, + &w->ov, + NULL + ); + if (!w->pending) + return FALSE; + } + + if (WaitForSingleObject(w->event, 0) == WAIT_OBJECT_0) { + GetOverlappedResult(w->dir, &w->ov, &bytes, FALSE); + w->pending = FALSE; + return TRUE; + } + + return FALSE; +} + +void DestroyDirectoryWatcher(DirectoryWatcher *w) +{ + if (w->dir != INVALID_HANDLE_VALUE) + CloseHandle(w->dir); + if (w->event) + CloseHandle(w->event); +} + +// +DirectoryWatcher w; +InitDirectoryWatcher(&w, L"C:\\mydir"); + +if (WasDirectoryModified(&w)) { + // something changed +} + +#if OS_LINUX +#include +#include +#include + +typedef struct { + int fd; + int wd; +} DirectoryWatcher; + +int InitDirectoryWatcher(DirectoryWatcher *w, const char *path) +{ + w->fd = inotify_init1(IN_NONBLOCK); + if (w->fd < 0) + return 0; + + w->wd = inotify_add_watch( + w->fd, + path, + IN_CREATE | IN_DELETE | IN_MOVE | IN_MODIFY + ); + + return w->wd >= 0; +} + +int WasDirectoryModified(DirectoryWatcher *w) +{ + char buffer[4096]; + ssize_t len = read(w->fd, buffer, sizeof(buffer)); + + if (len > 0) + return 1; + + return 0; // no data = no changes +} + +void DestroyDirectoryWatcher(DirectoryWatcher *w) +{ + if (w->wd >= 0) + inotify_rm_watch(w->fd, w->wd); + if (w->fd >= 0) + close(w->fd); +} +#endif + +#endif \ No newline at end of file diff --git a/src/text_editor/plugin_directory_navigation.h b/src/text_editor/plugin_directory_navigation.h new file mode 100644 index 0000000..40afacf --- /dev/null +++ b/src/text_editor/plugin_directory_navigation.h @@ -0,0 +1,3 @@ +#define PLUGIN_DIRECTORY_NAVIGATION +void InsertDirectoryNavigation(struct Buffer *buffer); +void OpenDirectoryNavigation(struct View *view); diff --git a/src/text_editor/plugin_search_open_buffers.cpp b/src/text_editor/plugin_search_open_buffers.cpp index 11c44c6..25822e7 100644 --- a/src/text_editor/plugin_search_open_buffers.cpp +++ b/src/text_editor/plugin_search_open_buffers.cpp @@ -13,9 +13,14 @@ void Coro_SearchOpenBuffers(mco_coro *co) { ForItem (id, buffers) { Buffer *it = GetBuffer(id, NULL); - if (it == NULL || it->special || it->is_dir || it->temp || it->dont_try_to_save_in_bulk_ops) { + if (it == NULL || it->special || it->temp || it->dont_try_to_save_in_bulk_ops) { continue; } +#ifdef PLUGIN_DIRECTORY_NAVIGATION + if (it->is_dir) { + continue; + } +#endif { Scratch scratch; diff --git a/src/text_editor/text_editor.cpp b/src/text_editor/text_editor.cpp index 26bd5a3..965ed92 100644 --- a/src/text_editor/text_editor.cpp +++ b/src/text_editor/text_editor.cpp @@ -14,6 +14,7 @@ #include "render/generated_font.cpp" #include "render/font.cpp" #include "render/opengl.cpp" +#include "plugin_directory_navigation.h" #include "text_editor.h" #include "plugin_command_window.h" #include "plugin_search_window.h" @@ -40,6 +41,7 @@ #include "test/tests.cpp" #include "commands_clipboard.cpp" +#include "plugin_directory_navigation.cpp" #include "plugin_search_open_buffers.cpp" #include "plugin_project_management.cpp" #include "plugin_basic_commands.cpp" diff --git a/src/text_editor/text_editor.h b/src/text_editor/text_editor.h index c9245de..61a0c33 100644 --- a/src/text_editor/text_editor.h +++ b/src/text_editor/text_editor.h @@ -63,7 +63,9 @@ struct Buffer { uint32_t dont_try_to_save_in_bulk_ops : 1; uint32_t close : 1; uint32_t special : 1; +#ifdef PLUGIN_DIRECTORY_NAVIGATION uint32_t is_dir : 1; +#endif }; };