diff options
author | pommicket <pommicket@gmail.com> | 2025-06-16 12:19:17 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-06-16 12:19:29 -0400 |
commit | 55b42079488e0c3e8aaf85828ad028491934f9d3 (patch) | |
tree | e69c7ec5fc8ba7ec82f1f8ddef692bc209b46d7b | |
parent | 5ad0bd2422e5b8bb23e50256837ae0c2538a85e0 (diff) |
better inotify
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | buffer.c | 84 | ||||
-rw-r--r-- | main.c | 10 | ||||
-rw-r--r-- | ted-internal.h | 21 | ||||
-rw-r--r-- | ted.c | 42 |
5 files changed, 106 insertions, 52 deletions
@@ -26,6 +26,7 @@ TAGS *.msi *.zip *.a +*.asc SDL2 *.deb .vs @@ -12,13 +12,6 @@ #elif _WIN32 #include <io.h> #endif -#if __linux__ -#include <sys/inotify.h> -#define HAS_INOTIFY 1 -#define INOTIFY_MASK (IN_ATTRIB | IN_CLOSE_WRITE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF) -#else -#define HAS_INOTIFY 0 -#endif /// A single line in a buffer typedef struct Line Line; @@ -91,6 +84,8 @@ struct TextBuffer { bool indent_with_spaces; /// true if user has manually specified indentation bool manual_indentation; + /// set to true in ted.c when buffer has been modified according to inotify + bool inotify_modified; /// tab size /// /// this is either set according to the user's settings or according to the autodetected indentation @@ -140,7 +135,7 @@ struct TextBuffer { /// dynamic array of redo history BufferEdit *redo_history; #if HAS_INOTIFY - int inotify_fd, inotify_watch; + int inotify_watch; #endif }; @@ -242,6 +237,7 @@ double buffer_last_write_time(TextBuffer *buffer) { void buffer_ignore_changes_on_disk(TextBuffer *buffer) { buffer->last_write_time = timespec_to_seconds(time_last_modified(buffer->path)); + buffer->inotify_modified = false; // no matter what, buffer_unsaved_changes should return true buffer->undo_history_write_pos = U32_MAX; } @@ -385,7 +381,6 @@ static void buffer_set_up(Ted *ted, TextBuffer *buffer) { buffer->indent_with_spaces = settings->indent_with_spaces; buffer->tab_width = settings->tab_width; #if HAS_INOTIFY - buffer->inotify_fd = -1; buffer->inotify_watch = -1; #endif } @@ -1040,9 +1035,8 @@ static void buffer_free_inner(TextBuffer *buffer) { arr_free(buffer->redo_history); settings_free(&buffer->settings); #if HAS_INOTIFY - if (buffer->inotify_fd != -1) { - close(buffer->inotify_fd); - buffer->inotify_fd = -1; + if (buffer->inotify_watch != -1) { + inotify_rm_watch(ted->inotify_fd, buffer->inotify_watch); buffer->inotify_watch = -1; } #endif @@ -3071,20 +3065,17 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { // it's important we do this first, since someone might write to the file while we're reading it, // and we want to detect that in buffer_externally_changed double modified_time = timespec_to_seconds(time_last_modified(path)); + Ted *ted = buffer->ted; #if HAS_INOTIFY - int inotify_fd = reload ? buffer->inotify_fd : -1, - inotify_watch = reload ? buffer->inotify_watch : -1; - if (inotify_fd == -1) { - inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); - if (inotify_fd == -1) { - perror("inotify_init1"); - } - } else if (inotify_watch != -1) { - inotify_rm_watch(inotify_fd, inotify_watch); + if (buffer->inotify_watch != -1) { + // important we remove it and recreate it to handle edge cases like + // "file was renamed and new file was created with the same name" + inotify_rm_watch(ted->inotify_fd, buffer->inotify_watch); + buffer->inotify_watch = -1; } - int inotify_err = 0; - if (inotify_fd != -1) { - inotify_watch = inotify_add_watch(inotify_fd, path, INOTIFY_MASK); + int inotify_err = 0, inotify_watch = -1; + if (ted->inotify_fd != -1) { + inotify_watch = inotify_add_watch(ted->inotify_fd, path, INOTIFY_MASK); if (inotify_watch == -1) inotify_err = errno; } #endif @@ -3103,7 +3094,7 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { success = false; } size_t file_size = 0; - const Settings *default_settings = ted_default_settings(buffer->ted); + const Settings *default_settings = ted_default_settings(ted); u32 max_file_size_editable = default_settings->max_file_size; u32 max_file_size_view_only = default_settings->max_file_size_view_only; if (success) { @@ -3224,13 +3215,13 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { // do this regardless of whether we were successful so we don't spam errors // if the file is deleted, for example. buffer->last_write_time = modified_time; + buffer->inotify_modified = false; if (success) { buffer->frame_earliest_line_modified = 0; buffer->frame_latest_line_modified = nlines - 1; buffer->undo_history_write_pos = arr_len(buffer->undo_history); #if HAS_INOTIFY buffer->inotify_watch = inotify_watch; - buffer->inotify_fd = inotify_fd; #endif if (!(fs_path_permission(path) & FS_PERMISSION_WRITE)) { // can't write to this file; make the buffer view only. @@ -3244,9 +3235,8 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { } if (!success) { #if HAS_INOTIFY - if (inotify_fd != -1) { - close(inotify_fd); - buffer->inotify_watch = buffer->inotify_fd = -1; + if (inotify_watch != -1) { + inotify_rm_watch(ted->inotify_fd, inotify_watch); } #endif // something went wrong; we need to free all the memory we used @@ -3272,29 +3262,11 @@ bool buffer_externally_changed(TextBuffer *buffer) { if (!buffer_is_named_file(buffer)) return false; double last_modified = timespec_to_seconds(time_last_modified(buffer->path)); - bool modified = last_modified != buffer->last_write_time; - #if HAS_INOTIFY // for whatever reason, last modified time seems to be very slightly unreliable on Linux // (occasionally we read a file and its contents later change shortly thereafter // w/o the modified time changing) // so we use inotify as a "backup" - if (buffer->inotify_watch != -1) { - char events[16384 * sizeof(struct inotify_event)]; - ssize_t bytes = read(buffer->inotify_fd, events, sizeof events); - for (int i = 0; i + (int)sizeof(struct inotify_event) <= bytes; i += (int)sizeof(struct inotify_event)) { - struct inotify_event event; - memcpy(&event, &events[i * (int)sizeof event], sizeof event); - if (event.mask & INOTIFY_MASK) - modified = true; - i += (int)event.len; // should never happen in theory... - } - // ideally we would sit in a loop here but then we'd hang if someone is constantly - // changing the file. probably no good way of dealing with that though. - // for what it's worth, 16384 is the default max inotify queue size, so we should always - // read all the events with that one read call. - } - #endif - return modified; + return last_modified != buffer->last_write_time || buffer->inotify_modified; } void buffer_new_file(TextBuffer *buffer, const char *path) { @@ -3317,10 +3289,11 @@ void buffer_new_file(TextBuffer *buffer, const char *path) { static bool buffer_write_to_file(TextBuffer *buffer, const char *path) { const Settings *settings = buffer_settings(buffer); + Ted *ted = buffer->ted; #if HAS_INOTIFY if (buffer->inotify_watch != -1) { // temporarily stop inotify events while we're writing to the buffer - inotify_rm_watch(buffer->inotify_fd, buffer->inotify_watch); + inotify_rm_watch(ted->inotify_fd, buffer->inotify_watch); buffer->inotify_watch = -1; } #endif @@ -3404,9 +3377,9 @@ static bool buffer_write_to_file(TextBuffer *buffer, const char *path) { success = false; } #if HAS_INOTIFY - if (buffer->inotify_fd != -1) { + if (ted->inotify_fd != -1) { // re-enable inotify - buffer->inotify_watch = inotify_add_watch(buffer->inotify_fd, path, INOTIFY_MASK); + buffer->inotify_watch = inotify_add_watch(ted->inotify_fd, path, INOTIFY_MASK); } #endif @@ -3456,6 +3429,7 @@ bool buffer_save(TextBuffer *buffer) { if (success && *backup_path) remove(backup_path); buffer->last_write_time = timespec_to_seconds(time_last_modified(buffer->path)); + buffer->inotify_modified = false; if (success) { buffer->undo_history_write_pos = arr_len(buffer->undo_history); const char *filename = path_filename(buffer->path); @@ -4268,3 +4242,11 @@ void buffer_set_manual_tab_width(TextBuffer *buffer, u8 n) { buffer->tab_width = n; buffer->manual_indentation = true; } + +int buffer_inotify_watch(TextBuffer *buffer) { + return buffer->inotify_watch; +} + +void buffer_set_inotify_modified(TextBuffer *buffer) { + buffer->inotify_modified = true; +} @@ -383,6 +383,7 @@ int main(int argc, char **argv) { if (!ted) { die("Not enough memory available to run ted."); } + ted->inotify_fd = -1; ted->last_save_time = -1e50; ted->pid = process_get_id(); ted_update_time(ted); @@ -604,6 +605,12 @@ int main(int argc, char **argv) { PROFILE_TIME(fonts_end) PROFILE_TIME(create_start) + #if HAS_INOTIFY + ted->inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); + if (ted->inotify_fd == -1) { + perror("inotify_init1"); + } + #endif { TextBuffer *lbuffer = ted->line_buffer = line_buffer_new(ted); if (!lbuffer || buffer_has_error(lbuffer)) @@ -901,6 +908,9 @@ int main(int argc, char **argv) { // check if active buffer should be reloaded { TextBuffer *active_buffer = ted->active_buffer; + #if HAS_INOTIFY + ted_check_inotify(ted); + #endif if (active_buffer && buffer_externally_changed(active_buffer)) { if (buffer_settings(active_buffer)->auto_reload) buffer_reload(active_buffer); diff --git a/ted-internal.h b/ted-internal.h index 466c6c3..a18228a 100644 --- a/ted-internal.h +++ b/ted-internal.h @@ -13,6 +13,13 @@ #include "ds.h" #include "sdl-inc.h" #include "lib/glcorearb.h" +#if __linux__ +#include <sys/inotify.h> +#define HAS_INOTIFY 1 +#define INOTIFY_MASK (IN_ATTRIB | IN_CLOSE_WRITE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF) +#else +#define HAS_INOTIFY 0 +#endif #if PROFILE #define PROFILE_TIME(var) double var = time_get_seconds(); @@ -436,7 +443,9 @@ struct Ted { MessageType message_type; MessageType message_shown_type; char message_shown[512]; - + + // file descriptor used to access inotify API on Linux + int inotify_fd; u64 edit_notify_id; EditNotifyInfo *edit_notifys; @@ -492,6 +501,14 @@ void buffer_center_cursor_next_frame(TextBuffer *buffer); void buffer_check_valid(TextBuffer *buffer); void buffer_publish_diagnostics(TextBuffer *buffer, const LSPRequest *request, LSPDiagnostic *diagnostics); void buffer_print_undo_history(TextBuffer *buffer); +#if HAS_INOTIFY +/// get watch descriptor for this buffer for inotify tracking +/// +/// returns -1 if there is none. +int buffer_inotify_watch(TextBuffer *buffer); +/// inform buffer that its watch on the Ted's inotify was modified +void buffer_set_inotify_modified(TextBuffer *buffer); +#endif // === build.c === void build_frame(Ted *ted, float x1, float y1, float x2, float y2); @@ -753,5 +770,7 @@ void ted_load_fonts(Ted *ted); void ted_free_fonts(Ted *ted); /// process textDocument/publishDiagnostics request void ted_process_publish_diagnostics(Ted *ted, LSP *lsp, LSPRequest *request); +/// check inotify fd for events +void ted_check_inotify(Ted *ted); #endif // TED_INTERNAL_H_ @@ -3,8 +3,11 @@ #include "ted-internal.h" #if _WIN32 #include <SDL_syswm.h> +#elif __unix__ + #include <unistd.h> #endif + struct LoadedFont { char *path; Font *font; @@ -978,3 +981,42 @@ void ted_test(Ted *ted) { #undef run_test printf("all good as far as i know :3\n"); } + +#if HAS_INOTIFY +void ted_check_inotify(Ted *ted) { + // see buffer_externally_changed definition for why this exists + if (ted->inotify_fd == -1) return; + int *watches_modified = NULL; + char events[16384 * sizeof(struct inotify_event)]; + ssize_t bytes = read(ted->inotify_fd, events, sizeof events); + for (int i = 0; i + (int)sizeof(struct inotify_event) <= bytes; i += (int)sizeof(struct inotify_event)) { + struct inotify_event event; + memcpy(&event, &events[i * (int)sizeof event], sizeof event); + if (event.mask & INOTIFY_MASK) { + bool new = true; + arr_foreach_ptr(watches_modified, int, w) { + if (*w == event.wd) { + new = false; + break; + } + } + if (new) arr_add(watches_modified, event.wd); + } + i += (int)event.len; // should always be 0 in theory... + } + // ideally we'd read more events in a loop here but then we'd hang if someone is constantly + // changing the file. probably no good way of dealing with that though. + // for what it's worth, 16384 is the default max inotify queue size, so we should always + // read all the events with that one read call. + arr_foreach_ptr(ted->buffers, TextBufferPtr, pbuffer) { + int watch = buffer_inotify_watch(*pbuffer); + arr_foreach_ptr(watches_modified, int, w) { + if (*w == watch) { + buffer_set_inotify_modified(*pbuffer); + break; + } + } + } + arr_free(watches_modified); +} +#endif |