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.
This commit is contained in:
Maxime Coste 2013-10-15 18:51:31 +01:00
parent fe0a4f0d11
commit 03c74b7a88
6 changed files with 81 additions and 2 deletions

View File

@ -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;
}
}

View File

@ -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<BufferChangeListener*> m_change_listeners;

View File

@ -12,6 +12,7 @@
#include "window.hh"
#include "file.hh"
#include "remote.hh"
#include "client_manager.hh"
#include <unordered_map>
@ -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();
});
}
}
}

View File

@ -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);

View File

@ -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<String>(crlf ? "crlf" : "lf");
@ -200,7 +201,7 @@ static void write(int fd, memoryview<char> 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<String>();
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<String> paths)
@ -305,4 +308,12 @@ std::vector<String> 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;
}
}

View File

@ -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<String> paths);
time_t get_fs_timestamp(const String& filename);
std::vector<String> complete_filename(const String& prefix,
const Regex& ignore_regex,
ByteCount cursor_pos = -1);