Merge remote-tracking branch 'eraserhd/history-api'

This commit is contained in:
Maxime Coste 2020-01-02 21:13:43 +11:00
commit 43dc494e5c
10 changed files with 119 additions and 28 deletions

View File

@ -258,6 +258,18 @@ The following expansions are supported (with required context _in italics_):
the text of the error that cancelled execution of the <commands> parameter
(or the previous <on_error_commands> parameter)
*%val{history}*::
_in buffer, window scope_ +
the full change history of the buffer, including undo forks, in terms
of `parent committed redo_child modification0 modification1 ...`
entries, where _parent_ is the index of the entry's predecessor (entry
0, which is the root of the history tree, will always have `-` here),
_committed_ is a count in seconds from Kakoune's steady clock's epoch
of the creation of the history entry, _redo_child_ is the index of the
child which will be visited for `U` (always `-` at the leaves of the
history), and each _modification_ is presented as in
`%val{uncommited_changes}`.
*%val{history_id}*::
_in buffer, window scope_ +
history id of the current buffer, an integer value which refers to a
@ -353,6 +365,15 @@ The following expansions are supported (with required context _in italics_):
buffer is modified, including undoing and redoing previous modifications
(see also `%val{history_id}`)
*%val{uncommitted_modifications}*::
_in buffer, window scope_ +
a list of quoted insertions (in the format `+line.column|text`) and
deletions (`-line.column|text`) not yet saved to the history (e.g. typing
in insert mode before pressing `<esc>`), where _line_ is the 1-based line
of the change, _column_ is the 1-based _byte_ of the change start (see
`%val{cursor_column}`), and _text_ is the content of the insertion or
deletion (see also `%val{history}`)
*%val{user_modes}*::
unquoted list of user modes.

View File

@ -66,7 +66,7 @@ static void apply_options(OptionManager& options, const ParsedLines& parsed_line
}
Buffer::HistoryNode::HistoryNode(HistoryId parent)
: parent{parent}, timepoint{Clock::now()}
: parent{parent}, committed{Clock::now()}
{}
Buffer::Buffer(String name, Flags flags, StringView data,
@ -231,20 +231,10 @@ String Buffer::string(BufferCoord begin, BufferCoord end) const
return res;
}
// A Modification holds a single atomic modification to Buffer
struct Buffer::Modification
Buffer::Modification Buffer::Modification::inverse() const
{
enum Type { Insert, Erase };
Type type;
BufferCoord coord;
StringDataPtr content;
Modification inverse() const
{
return {type == Insert ? Erase : Insert, coord, content};
}
};
return {type == Insert ? Erase : Insert, coord, content};
}
void Buffer::reload(StringView data, timespec fs_timestamp)
{

View File

@ -230,14 +230,40 @@ public:
void on_unregistered();
void throw_if_read_only() const;
// A Modification holds a single atomic modification to Buffer
struct Modification
{
enum Type { Insert, Erase };
Type type;
BufferCoord coord;
StringDataPtr content;
Modification inverse() const;
};
using UndoGroup = Vector<Modification, MemoryDomain::BufferMeta>;
struct HistoryNode : UseMemoryDomain<MemoryDomain::BufferMeta>
{
HistoryNode(HistoryId parent);
HistoryId parent;
HistoryId redo_child = HistoryId::Invalid;
TimePoint committed;
UndoGroup undo_group;
};
const Vector<HistoryNode>& history() const { return m_history; }
const UndoGroup& current_undo_group() const { return m_current_undo_group; }
private:
void on_option_changed(const Option& option) override;
BufferRange do_insert(BufferCoord pos, StringView content);
BufferCoord do_erase(BufferCoord begin, BufferCoord end);
struct Modification;
void apply_modification(const Modification& modification);
void revert_modification(const Modification& modification);
@ -264,18 +290,6 @@ private:
String m_display_name;
Flags m_flags;
using UndoGroup = Vector<Modification, MemoryDomain::BufferMeta>;
struct HistoryNode : UseMemoryDomain<MemoryDomain::BufferMeta>
{
HistoryNode(HistoryId parent);
HistoryId parent;
HistoryId redo_child = HistoryId::Invalid;
TimePoint timepoint;
UndoGroup undo_group;
};
Vector<HistoryNode> m_history;
HistoryId m_history_id = HistoryId::Invalid;
HistoryId m_last_save_history_id = HistoryId::Invalid;

View File

@ -219,4 +219,47 @@ void write_to_debug_buffer(StringView str)
}
}
InplaceString<23> to_string(Buffer::HistoryId id)
{
if (id == Buffer::HistoryId::Invalid) {
InplaceString<23> res;
res.m_data[0] = '-';
res.m_length = 1;
return res;
} else {
return to_string(static_cast<size_t>(id));
}
}
String format_modification(const Buffer::Modification& modification, Quoting quoting)
{
auto quote = quoter(quoting);
return quote(format("{}{}.{}|{}",
modification.type == Buffer::Modification::Type::Insert ? '+' : '-',
modification.coord.line, modification.coord.column,
modification.content->strview()));
}
String history_as_string(const Vector<Buffer::HistoryNode>& history, Quoting quoting)
{
auto format_history_node = [&](const Buffer::HistoryNode& node) {
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(node.committed.time_since_epoch());
return format("{} {} {}{}{}",
node.parent,
seconds.count(),
node.redo_child,
node.undo_group.empty() ? "" : " ",
undo_group_as_string(node.undo_group, quoting));
};
return join(history |transform(format_history_node), ' ', false);
}
String undo_group_as_string(const Buffer::UndoGroup& undo_group, Quoting quoting)
{
auto modification_as_string = [&](const Buffer::Modification& modification) {
return format_modification(modification, quoting);
};
return join(undo_group |transform(modification_as_string), ' ', false);
}
}

View File

@ -84,6 +84,11 @@ void reload_file_buffer(Buffer& buffer);
void write_to_debug_buffer(StringView str);
InplaceString<23> to_string(Buffer::HistoryId id);
String format_modification(const Buffer::Modification& modification, Quoting quoting);
String history_as_string(const Vector<Buffer::HistoryNode>& history, Quoting quoting);
String undo_group_as_string(const Buffer::UndoGroup& undo_group, Quoting quoting);
}
#endif // buffer_utils_hh_INCLUDED

View File

@ -309,6 +309,14 @@ static const EnvVarDesc builtin_env_vars[] = { {
return format("{} {} {} {}", setup.window_pos.line, setup.window_pos.column,
setup.window_range.line, setup.window_range.column);
}
}, {
"history", false,
[](StringView name, const Context& context, Quoting quoting) -> String
{ return history_as_string(context.buffer().history(), quoting); }
}, {
"uncommitted_modifications", false,
[](StringView name, const Context& context, Quoting quoting) -> String
{ return undo_group_as_string(context.buffer().current_undo_group(), quoting); }
}
};

1
test/compose/history/cmd Normal file
View File

@ -0,0 +1 @@
Amiddle<esc><a-f>dd

1
test/compose/history/in Normal file
View File

@ -0,0 +1 @@
start

View File

@ -0,0 +1 @@
- $timestamp 1 0 $timestamp - '+0.5|m' '+0.6|i' '+0.7|d' '+0.8|d' '+0.9|l' '+0.10|e' '-0.8|dle'

7
test/compose/history/rc Normal file
View File

@ -0,0 +1,7 @@
# Make our expansion have a predictable timestamp
hook global ClientClose .* %{
evaluate-commands %sh{
kak -f 'ghf<space>ec$timestamp<esc>2f<space>ec$timestamp<esc>' <kak_quoted_history >tmp
mv tmp kak_quoted_history
}
}