From 03c74b7a885ab116ed88907d98bf3e314e019cc1 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 15 Oct 2013 18:51:31 +0100 Subject: [PATCH] Detect file external modification and ask the user whether to reload or not * Buffer now store a m_fs_timestamp field. * Client in Normal mode checks current buffer file every 500 ms, or each time it goes back to Normal mode. --- src/buffer.cc | 12 ++++++++++++ src/buffer.hh | 4 ++++ src/client.cc | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/client.hh | 2 ++ src/file.cc | 13 ++++++++++++- src/file.hh | 4 +++- 6 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index 770be530..0470884d 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -708,4 +708,16 @@ char Buffer::byte_at(BufferCoord c) const return m_lines[c.line].content[c.column]; } +time_t Buffer::fs_timestamp() const +{ + kak_assert(m_flags & Flags::File); + return m_fs_timestamp; +} + +void Buffer::set_fs_timestamp(time_t ts) +{ + kak_assert(m_flags & Flags::File); + m_fs_timestamp = ts; +} + } diff --git a/src/buffer.hh b/src/buffer.hh index fcc18a12..d1bad814 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -104,6 +104,8 @@ public: BufferIterator erase(BufferIterator begin, BufferIterator end); size_t timestamp() const { return m_timestamp; } + time_t fs_timestamp() const; + void set_fs_timestamp(time_t ts); void commit_undo_group(); bool undo(); @@ -197,6 +199,8 @@ private: size_t m_last_save_undo_index; size_t m_timestamp; + time_t m_fs_timestamp; + // this is mutable as adding or removing listeners is not muting the // buffer observable state. mutable std::unordered_set m_change_listeners; diff --git a/src/client.cc b/src/client.cc index 3e92727c..852d469e 100644 --- a/src/client.cc +++ b/src/client.cc @@ -12,6 +12,7 @@ #include "window.hh" #include "file.hh" #include "remote.hh" +#include "client_manager.hh" #include @@ -45,6 +46,7 @@ namespace InputModes { static constexpr std::chrono::milliseconds idle_timeout{100}; +static constexpr std::chrono::milliseconds fs_check_timeout{500}; class Normal : public InputMode { @@ -53,8 +55,13 @@ public: : InputMode(client), m_idle_timer{Clock::now() + idle_timeout, [this](Timer& timer) { context().hooks().run_hook("NormalIdle", "", context()); + }}, + m_fs_check_timer{Clock::now() + fs_check_timeout, [this](Timer& timer) { + context().client().check_buffer_fs_timestamp(); + timer.set_next_date(Clock::now() + fs_check_timeout); }} { + context().client().check_buffer_fs_timestamp(); context().hooks().run_hook("NormalBegin", "", context()); } @@ -87,6 +94,7 @@ public: private: int m_count = 0; Timer m_idle_timer; + Timer m_fs_check_timer; }; class LineEditor @@ -1095,4 +1103,44 @@ void Client::reset_normal_mode() change_input_mode(new InputModes::Normal(*this)); } +void Client::check_buffer_fs_timestamp() +{ + Buffer& buffer = m_context.buffer(); + const String& filename = buffer.name(); + if (not (buffer.flags() & Buffer::Flags::File)) + return; + time_t ts = get_fs_timestamp(filename); + if (ts != buffer.fs_timestamp()) + { + print_status({"'" + buffer.display_name() + "' was modified externally, press r or y to reload, k or n to keep", get_color("Prompt")}); + on_next_key([this, ts, filename](Key key, Context& context) { + Buffer* buf = BufferManager::instance().get_buffer_ifp(filename); + // buffer got deleted while waiting for the key, do nothing + if (not buf) + { + print_status({}); + return; + } + if (key == 'r' or key == 'y') + { + DisplayCoord view_pos = context.window().position(); + BufferCoord cursor_pos = context.editor().main_selection().last(); + buf = create_buffer_from_file(filename); + Window& win = ClientManager::instance().get_unused_window_for_buffer(*buf); + win.select(cursor_pos); + win.set_position(view_pos); + context.change_editor(win); + print_status({"'" + buf->display_name() + "' reloaded", get_color("Information") }); + } + if (key == 'k' or key == 'n') + { + buf->set_fs_timestamp(ts); + print_status({"'" + buf->display_name() + "' kept", get_color("Information") }); + } + else + check_buffer_fs_timestamp(); + }); + } +} + } diff --git a/src/client.hh b/src/client.hh index 3cbb15d9..de3319d2 100644 --- a/src/client.hh +++ b/src/client.hh @@ -86,6 +86,8 @@ public: UserInterface& ui() const { return *m_ui; } + void check_buffer_fs_timestamp(); + void reset_normal_mode(); private: void change_input_mode(InputMode* new_mode); diff --git a/src/file.cc b/src/file.cc index 3c93f460..18682ac3 100644 --- a/src/file.cc +++ b/src/file.cc @@ -176,6 +176,7 @@ Buffer* create_buffer_from_file(String filename) pos = line_end + 1; } Buffer* buffer = new Buffer(filename, Buffer::Flags::File, std::move(lines)); + buffer->set_fs_timestamp(st.st_mtime); OptionManager& options = buffer->options(); options.get_local_option("eolformat").set(crlf ? "crlf" : "lf"); @@ -200,7 +201,7 @@ static void write(int fd, memoryview data, const String& filename) } } -void write_buffer_to_file(const Buffer& buffer, const String& filename) +void write_buffer_to_file(Buffer& buffer, const String& filename) { String eolformat = buffer.options()["eolformat"].get(); if (eolformat == "crlf") @@ -226,6 +227,8 @@ void write_buffer_to_file(const Buffer& buffer, const String& filename) write(fd, linedata.subrange(0, linedata.size()-1), filename); write(fd, eoldata, filename); } + if ((buffer.flags() & Buffer::Flags::File) and filename == buffer.name()) + buffer.set_fs_timestamp(get_fs_timestamp(filename)); } String find_file(const String& filename, memoryview paths) @@ -305,4 +308,12 @@ std::vector complete_filename(const String& prefix, return real_result; } +time_t get_fs_timestamp(const String& filename) +{ + struct stat st; + if (stat(filename.c_str(), &st) != 0) + throw runtime_error("stat failed on " + filename); + return st.st_mtime; +} + } diff --git a/src/file.hh b/src/file.hh index 4a93117f..5a5bddd6 100644 --- a/src/file.hh +++ b/src/file.hh @@ -30,9 +30,11 @@ String compact_path(const String& filename); String read_file(const String& filename); Buffer* create_buffer_from_file(String filename); -void write_buffer_to_file(const Buffer& buffer, const String& filename); +void write_buffer_to_file(Buffer& buffer, const String& filename); String find_file(const String& filename, memoryview paths); +time_t get_fs_timestamp(const String& filename); + std::vector complete_filename(const String& prefix, const Regex& ignore_regex, ByteCount cursor_pos = -1);