2011-09-02 18:51:20 +02:00
|
|
|
#include "buffer.hh"
|
2011-09-08 02:11:48 +02:00
|
|
|
|
2011-09-09 21:24:18 +02:00
|
|
|
#include "assert.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
#include "buffer_manager.hh"
|
2017-04-27 21:23:23 +02:00
|
|
|
#include "buffer_utils.hh"
|
2014-10-01 01:20:12 +02:00
|
|
|
#include "client.hh"
|
2012-01-23 14:56:43 +01:00
|
|
|
#include "context.hh"
|
2015-05-22 14:58:56 +02:00
|
|
|
#include "diff.hh"
|
2013-03-25 19:58:23 +01:00
|
|
|
#include "file.hh"
|
2017-03-15 19:25:59 +01:00
|
|
|
#include "flags.hh"
|
2017-03-22 22:08:52 +01:00
|
|
|
#include "option_types.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "ranges.hh"
|
2015-01-15 14:54:38 +01:00
|
|
|
#include "shared_string.hh"
|
2015-05-22 14:58:56 +02:00
|
|
|
#include "unit_tests.hh"
|
2013-04-09 20:05:40 +02:00
|
|
|
#include "utils.hh"
|
|
|
|
#include "window.hh"
|
2011-09-08 02:11:48 +02:00
|
|
|
|
2012-03-06 15:27:03 +01:00
|
|
|
#include <algorithm>
|
|
|
|
|
2011-09-02 18:51:20 +02:00
|
|
|
namespace Kakoune
|
|
|
|
{
|
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
Buffer::HistoryNode::HistoryNode()
|
|
|
|
: committed{Clock::now()}
|
2016-07-20 09:50:38 +02:00
|
|
|
{}
|
2016-07-19 23:03:47 +02:00
|
|
|
|
2021-05-28 09:03:06 +02:00
|
|
|
Buffer::Buffer(String name, Flags flags, BufferLines lines,
|
|
|
|
ByteOrderMark bom, EolFormat eolformat,
|
|
|
|
FsStatus fs_status)
|
2016-07-19 23:03:47 +02:00
|
|
|
: Scope{GlobalScope::instance()},
|
|
|
|
m_name{(flags & Flags::File) ? real_path(parse_filename(name)) : std::move(name)},
|
|
|
|
m_display_name{(flags & Flags::File) ? compact_path(m_name) : m_name},
|
|
|
|
m_flags{flags | Flags::NoUndo},
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
m_history{{HistoryNode{}}},
|
|
|
|
m_history_id{0},
|
|
|
|
m_last_save_history_id{0},
|
2021-05-28 09:03:06 +02:00
|
|
|
m_fs_status{fs_status}
|
2011-09-02 18:51:20 +02:00
|
|
|
{
|
2015-02-16 23:27:40 +01:00
|
|
|
#ifdef KAK_DEBUG
|
2021-05-28 09:03:06 +02:00
|
|
|
for (auto& line : lines)
|
2015-02-16 23:27:40 +01:00
|
|
|
kak_assert(not (line->length == 0) and
|
|
|
|
line->data()[line->length-1] == '\n');
|
|
|
|
#endif
|
2021-05-28 09:03:06 +02:00
|
|
|
static_cast<BufferLines&>(m_lines) = std::move(lines);
|
2012-08-14 14:13:10 +02:00
|
|
|
|
2017-06-11 13:01:40 +02:00
|
|
|
m_changes.push_back({ Change::Insert, {0,0}, line_count() });
|
2014-05-11 13:20:59 +02:00
|
|
|
|
2021-05-28 09:03:06 +02:00
|
|
|
options().get_local_option("eolformat").set(eolformat);
|
|
|
|
options().get_local_option("BOM").set(bom);
|
2015-10-16 14:52:14 +02:00
|
|
|
|
2016-07-10 17:01:33 +02:00
|
|
|
// now we may begin to record undo data
|
|
|
|
if (not (flags & Flags::NoUndo))
|
|
|
|
m_flags &= ~Flags::NoUndo;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Buffer::on_registered()
|
|
|
|
{
|
|
|
|
// Ignore debug buffer, as it can be created in many
|
|
|
|
// corner cases (including while destroying the BufferManager
|
2016-08-14 21:05:57 +02:00
|
|
|
// if a BufClose hooks triggers writing to it).
|
2016-07-10 17:01:33 +02:00
|
|
|
if (m_flags & Flags::Debug)
|
|
|
|
return;
|
|
|
|
|
|
|
|
options().register_watcher(*this);
|
|
|
|
|
2018-02-18 04:48:24 +01:00
|
|
|
if (m_flags & Buffer::Flags::NoHooks)
|
|
|
|
{
|
|
|
|
on_option_changed(options()["readonly"]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-22 23:15:53 +02:00
|
|
|
run_hook_in_own_context(Hook::BufCreate, m_name);
|
2017-06-07 20:46:43 +02:00
|
|
|
|
2016-07-10 17:01:33 +02:00
|
|
|
if (m_flags & Flags::File)
|
2013-04-12 19:11:28 +02:00
|
|
|
{
|
2016-07-10 17:01:33 +02:00
|
|
|
if (m_flags & Buffer::Flags::New)
|
2018-10-22 23:15:53 +02:00
|
|
|
run_hook_in_own_context(Hook::BufNewFile, m_name);
|
2013-04-12 19:11:28 +02:00
|
|
|
else
|
2013-10-17 19:47:09 +02:00
|
|
|
{
|
2019-11-23 15:55:46 +01:00
|
|
|
kak_assert(m_fs_status.timestamp != InvalidTime);
|
2018-10-22 23:15:53 +02:00
|
|
|
run_hook_in_own_context(Hook::BufOpenFile, m_name);
|
2013-10-17 19:47:09 +02:00
|
|
|
}
|
2013-04-12 19:11:28 +02:00
|
|
|
}
|
2012-06-12 20:27:57 +02:00
|
|
|
|
2018-05-21 12:07:25 +02:00
|
|
|
for (auto& option : options().flatten_options()
|
|
|
|
| transform(&std::unique_ptr<Option>::get)
|
|
|
|
| gather<Vector<Option*>>())
|
2013-11-12 21:36:42 +01:00
|
|
|
on_option_changed(*option);
|
2011-09-02 18:51:20 +02:00
|
|
|
}
|
|
|
|
|
2016-07-10 17:01:33 +02:00
|
|
|
void Buffer::on_unregistered()
|
2011-10-24 16:23:13 +02:00
|
|
|
{
|
2016-07-10 17:01:33 +02:00
|
|
|
if (m_flags & Flags::Debug)
|
|
|
|
return;
|
2012-08-05 20:12:43 +02:00
|
|
|
|
2014-10-30 15:00:42 +01:00
|
|
|
options().unregister_watcher(*this);
|
2018-10-22 23:15:53 +02:00
|
|
|
run_hook_in_own_context(Hook::BufClose, m_name);
|
2016-07-10 17:01:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Buffer::~Buffer()
|
|
|
|
{
|
2014-01-11 20:05:09 +01:00
|
|
|
m_values.clear();
|
2011-10-24 16:23:13 +02:00
|
|
|
}
|
|
|
|
|
2013-04-22 13:48:18 +02:00
|
|
|
bool Buffer::set_name(String name)
|
|
|
|
{
|
|
|
|
Buffer* other = BufferManager::instance().get_buffer_ifp(name);
|
|
|
|
if (other == nullptr or other == this)
|
|
|
|
{
|
|
|
|
if (m_flags & Flags::File)
|
2015-09-01 14:59:40 +02:00
|
|
|
{
|
2013-04-22 13:48:18 +02:00
|
|
|
m_name = real_path(name);
|
2015-09-01 14:59:40 +02:00
|
|
|
m_display_name = compact_path(m_name);
|
2019-12-07 05:27:23 +01:00
|
|
|
if (m_flags & Buffer::Flags::File and not file_exists(m_name))
|
|
|
|
{
|
|
|
|
m_flags |= Buffer::Flags::New;
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
m_last_save_history_id = InvalidHistoryId;
|
2019-12-07 05:27:23 +01:00
|
|
|
}
|
2015-09-01 14:59:40 +02:00
|
|
|
}
|
2013-04-22 13:48:18 +02:00
|
|
|
else
|
2015-09-01 14:59:40 +02:00
|
|
|
{
|
2013-04-22 13:48:18 +02:00
|
|
|
m_name = std::move(name);
|
2015-09-01 14:59:40 +02:00
|
|
|
m_display_name = m_name;
|
|
|
|
}
|
2013-04-22 13:48:18 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-02-11 03:06:19 +01:00
|
|
|
void Buffer::throw_if_read_only() const
|
|
|
|
{
|
|
|
|
if (m_flags & Flags::ReadOnly)
|
2018-04-06 16:56:53 +02:00
|
|
|
throw runtime_error("buffer is read-only");
|
2018-02-11 03:06:19 +01:00
|
|
|
}
|
|
|
|
|
2015-12-01 14:42:42 +01:00
|
|
|
void Buffer::update_display_name()
|
|
|
|
{
|
|
|
|
if (m_flags & Flags::File)
|
|
|
|
m_display_name = compact_path(m_name);
|
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferIterator Buffer::iterator_at(BufferCoord coord) const
|
2011-09-02 18:51:20 +02:00
|
|
|
{
|
2017-06-15 18:25:37 +02:00
|
|
|
kak_assert(is_valid(coord));
|
|
|
|
return {*this, coord};
|
2011-09-02 18:51:20 +02:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferCoord Buffer::clamp(BufferCoord coord) const
|
2011-09-02 18:51:20 +02:00
|
|
|
{
|
2017-11-07 16:56:24 +01:00
|
|
|
if (coord > back_coord())
|
|
|
|
coord = back_coord();
|
|
|
|
kak_assert(coord.line >= 0 and coord.line < line_count());
|
2013-06-05 18:41:02 +02:00
|
|
|
ByteCount max_col = std::max(0_byte, m_lines[coord.line].length() - 1);
|
2013-05-30 14:17:19 +02:00
|
|
|
coord.column = Kakoune::clamp(coord.column, 0_byte, max_col);
|
|
|
|
return coord;
|
2011-09-02 18:51:20 +02:00
|
|
|
}
|
|
|
|
|
2022-06-08 11:43:03 +02:00
|
|
|
BufferCoord Buffer::offset_coord(BufferCoord coord, CharCount offset, ColumnCount) const
|
2013-12-15 15:14:52 +01:00
|
|
|
{
|
2018-02-24 11:32:11 +01:00
|
|
|
return utf8::advance(iterator_at(coord), offset < 0 ? begin() : end()-1, offset).coord();
|
2013-12-15 15:14:52 +01:00
|
|
|
}
|
|
|
|
|
2022-06-08 11:43:03 +02:00
|
|
|
BufferCoordAndTarget Buffer::offset_coord(BufferCoordAndTarget coord, LineCount offset, ColumnCount tabstop) const
|
2013-12-15 15:14:52 +01:00
|
|
|
{
|
2017-05-21 09:24:44 +02:00
|
|
|
const auto column = coord.target == -1 ? get_column(*this, tabstop, coord) : coord.target;
|
2022-06-08 11:43:03 +02:00
|
|
|
const bool avoid_eol = coord.target < max_column;
|
2017-05-21 09:24:44 +02:00
|
|
|
const auto line = Kakoune::clamp(coord.line + offset, 0_line, line_count()-1);
|
|
|
|
const auto max_column = get_column(*this, tabstop, {line, m_lines[line].length()-1});
|
2017-05-22 18:04:01 +02:00
|
|
|
const auto final_column = std::max(0_col, std::min(column, max_column - (avoid_eol ? 1 : 0)));
|
2017-04-27 21:23:23 +02:00
|
|
|
return {line, get_byte_to_column(*this, tabstop, {line, final_column}), column};
|
2013-12-15 15:14:52 +01:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
String Buffer::string(BufferCoord begin, BufferCoord end) const
|
2011-09-02 18:51:20 +02:00
|
|
|
{
|
2012-03-30 13:37:18 +02:00
|
|
|
String res;
|
2017-06-07 12:40:13 +02:00
|
|
|
const auto last_line = std::min(end.line, line_count()-1);
|
|
|
|
for (auto line = begin.line; line <= last_line; ++line)
|
2012-03-30 13:37:18 +02:00
|
|
|
{
|
2013-06-27 23:49:34 +02:00
|
|
|
ByteCount start = 0;
|
|
|
|
if (line == begin.line)
|
|
|
|
start = begin.column;
|
|
|
|
ByteCount count = -1;
|
|
|
|
if (line == end.line)
|
|
|
|
count = end.column - start;
|
2014-05-24 18:08:01 +02:00
|
|
|
res += m_lines[line].substr(start, count);
|
2012-03-30 13:37:18 +02:00
|
|
|
}
|
|
|
|
return res;
|
2011-09-02 18:51:20 +02:00
|
|
|
}
|
|
|
|
|
2018-12-31 18:40:49 +01:00
|
|
|
Buffer::Modification Buffer::Modification::inverse() const
|
2011-09-06 20:49:32 +02:00
|
|
|
{
|
2018-12-31 18:40:49 +01:00
|
|
|
return {type == Insert ? Erase : Insert, coord, content};
|
|
|
|
}
|
2011-09-06 20:49:32 +02:00
|
|
|
|
2021-05-28 09:03:06 +02:00
|
|
|
void Buffer::reload(BufferLines lines, ByteOrderMark bom, EolFormat eolformat, FsStatus fs_status)
|
2014-10-23 22:04:05 +02:00
|
|
|
{
|
2015-05-13 21:54:44 +02:00
|
|
|
const bool record_undo = not (m_flags & Flags::NoUndo);
|
2014-10-23 22:04:05 +02:00
|
|
|
|
|
|
|
commit_undo_group();
|
|
|
|
|
2015-05-14 15:05:02 +02:00
|
|
|
if (not record_undo)
|
|
|
|
{
|
2017-08-04 06:39:28 +02:00
|
|
|
// Erase history about to be invalidated history
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
m_history_id = 0;
|
|
|
|
m_last_save_history_id = 0;
|
|
|
|
m_history = {HistoryNode{}};
|
2015-05-14 15:05:02 +02:00
|
|
|
|
2017-08-04 06:39:28 +02:00
|
|
|
m_changes.push_back({ Change::Erase, {0,0}, line_count() });
|
2021-05-28 09:03:06 +02:00
|
|
|
static_cast<BufferLines&>(m_lines) = std::move(lines);
|
2017-06-11 13:01:40 +02:00
|
|
|
m_changes.push_back({ Change::Insert, {0,0}, line_count() });
|
2015-05-14 15:05:02 +02:00
|
|
|
}
|
|
|
|
else
|
2014-10-23 22:04:05 +02:00
|
|
|
{
|
2019-11-30 00:46:42 +01:00
|
|
|
Vector<Diff> diff;
|
|
|
|
for_each_diff(m_lines.begin(), m_lines.size(),
|
2021-05-28 09:03:06 +02:00
|
|
|
lines.begin(), lines.size(),
|
2020-03-20 10:26:05 +01:00
|
|
|
[&diff](DiffOp op, int len)
|
|
|
|
{ diff.push_back({op, len}); },
|
2019-11-30 00:46:42 +01:00
|
|
|
[](const StringDataPtr& lhs, const StringDataPtr& rhs)
|
|
|
|
{ return lhs->strview() == rhs->strview(); });
|
2015-05-14 15:05:02 +02:00
|
|
|
|
|
|
|
auto it = m_lines.begin();
|
2021-05-28 09:03:06 +02:00
|
|
|
auto new_it = lines.begin();
|
2015-05-14 15:05:02 +02:00
|
|
|
for (auto& d : diff)
|
2015-05-13 21:54:44 +02:00
|
|
|
{
|
2019-11-30 00:46:42 +01:00
|
|
|
if (d.op == DiffOp::Keep)
|
2020-03-20 10:26:05 +01:00
|
|
|
{
|
2015-05-14 15:05:02 +02:00
|
|
|
it += d.len;
|
2020-03-20 10:26:05 +01:00
|
|
|
new_it += d.len;
|
|
|
|
}
|
2019-11-30 00:46:42 +01:00
|
|
|
else if (d.op == DiffOp::Add)
|
2015-05-13 21:54:44 +02:00
|
|
|
{
|
|
|
|
const LineCount cur_line = (int)(it - m_lines.begin());
|
2015-05-14 15:05:02 +02:00
|
|
|
|
|
|
|
for (LineCount line = 0; line < d.len; ++line)
|
2020-03-20 10:26:05 +01:00
|
|
|
m_current_undo_group.push_back({Modification::Insert, cur_line + line, *(new_it + (int)line)});
|
2015-05-14 15:05:02 +02:00
|
|
|
|
2020-03-20 10:26:05 +01:00
|
|
|
m_changes.push_back({Change::Insert, cur_line, cur_line + d.len});
|
|
|
|
m_lines.insert(it, new_it, new_it + d.len);
|
2015-05-26 20:04:29 +02:00
|
|
|
it = m_lines.begin() + (int)(cur_line + d.len);
|
2020-03-20 10:26:05 +01:00
|
|
|
new_it += d.len;
|
2015-05-13 21:54:44 +02:00
|
|
|
}
|
2019-11-30 00:46:42 +01:00
|
|
|
else if (d.op == DiffOp::Remove)
|
2015-05-13 21:54:44 +02:00
|
|
|
{
|
|
|
|
const LineCount cur_line = (int)(it - m_lines.begin());
|
2015-05-14 15:05:02 +02:00
|
|
|
|
|
|
|
for (LineCount line = d.len-1; line >= 0; --line)
|
2017-09-01 12:31:37 +02:00
|
|
|
m_current_undo_group.push_back({
|
2015-05-14 15:05:02 +02:00
|
|
|
Modification::Erase, cur_line + line,
|
2017-09-01 12:31:37 +02:00
|
|
|
m_lines.get_storage(cur_line + line)});
|
2015-05-14 15:05:02 +02:00
|
|
|
|
2015-05-13 21:54:44 +02:00
|
|
|
it = m_lines.erase(it, it + d.len);
|
2017-06-11 13:01:40 +02:00
|
|
|
m_changes.push_back({ Change::Erase, cur_line, cur_line + d.len });
|
2015-05-13 21:54:44 +02:00
|
|
|
}
|
|
|
|
}
|
2014-10-23 22:04:05 +02:00
|
|
|
}
|
2015-01-22 14:39:29 +01:00
|
|
|
|
2014-10-23 22:04:05 +02:00
|
|
|
commit_undo_group();
|
2014-11-01 20:35:27 +01:00
|
|
|
|
2021-05-28 09:03:06 +02:00
|
|
|
options().get_local_option("eolformat").set(eolformat);
|
|
|
|
options().get_local_option("BOM").set(bom);
|
|
|
|
|
2015-10-16 14:52:14 +02:00
|
|
|
|
2018-05-02 13:31:44 +02:00
|
|
|
m_last_save_history_id = m_history_id;
|
2021-05-28 09:03:06 +02:00
|
|
|
m_fs_status = fs_status;
|
2014-10-23 22:04:05 +02:00
|
|
|
}
|
2013-04-25 14:03:55 +02:00
|
|
|
|
|
|
|
void Buffer::commit_undo_group()
|
|
|
|
{
|
|
|
|
if (m_flags & Flags::NoUndo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_current_undo_group.empty())
|
|
|
|
return;
|
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
// walk back to current position in history, and append reverse events
|
|
|
|
for (size_t current = m_history.size() - 1; current != m_history_id ; --current)
|
|
|
|
{
|
|
|
|
m_history.emplace_back();
|
|
|
|
m_history.back().undo_group = m_history[current].undo_group
|
|
|
|
| reverse()
|
|
|
|
| transform([](auto& modif) { return modif.inverse(); })
|
|
|
|
| gather<UndoGroup>();
|
|
|
|
}
|
|
|
|
m_history.emplace_back();
|
2018-05-02 13:31:44 +02:00
|
|
|
m_history.back().undo_group = std::move(m_current_undo_group);
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
|
2013-04-25 14:03:55 +02:00
|
|
|
m_current_undo_group.clear();
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
m_history_id = m_history.size() - 1;
|
2013-04-25 14:03:55 +02:00
|
|
|
}
|
|
|
|
|
2018-02-11 03:06:19 +01:00
|
|
|
bool Buffer::undo(size_t count)
|
2011-09-06 20:49:32 +02:00
|
|
|
{
|
2018-02-11 03:06:19 +01:00
|
|
|
throw_if_read_only();
|
|
|
|
|
2013-02-20 14:23:52 +01:00
|
|
|
commit_undo_group();
|
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
if ((m_history_id - 1) == InvalidHistoryId)
|
2011-09-06 20:49:32 +02:00
|
|
|
return false;
|
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
while (count-- != 0 and (m_history_id - 1) != InvalidHistoryId)
|
2016-07-20 21:20:03 +02:00
|
|
|
{
|
2018-05-02 13:31:44 +02:00
|
|
|
for (const Modification& modification : current_history_node().undo_group | reverse())
|
2016-07-20 21:20:03 +02:00
|
|
|
apply_modification(modification.inverse());
|
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
m_history_id--;
|
2016-07-20 21:20:03 +02:00
|
|
|
}
|
2016-07-19 23:03:47 +02:00
|
|
|
|
2012-06-05 15:33:02 +02:00
|
|
|
return true;
|
2011-09-06 20:49:32 +02:00
|
|
|
}
|
|
|
|
|
2018-02-11 03:06:19 +01:00
|
|
|
bool Buffer::redo(size_t count)
|
2011-09-06 20:49:32 +02:00
|
|
|
{
|
2018-02-11 03:06:19 +01:00
|
|
|
throw_if_read_only();
|
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
if (m_history_id == (m_history.size() - 1))
|
2011-09-06 20:49:32 +02:00
|
|
|
return false;
|
|
|
|
|
2013-04-09 20:04:11 +02:00
|
|
|
kak_assert(m_current_undo_group.empty());
|
2013-02-20 14:23:52 +01:00
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
while (count-- != 0 and m_history_id != (m_history.size() - 1))
|
2016-07-20 21:20:03 +02:00
|
|
|
{
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
m_history_id++;
|
2011-09-06 20:49:32 +02:00
|
|
|
|
2018-05-02 13:31:44 +02:00
|
|
|
for (const Modification& modification : current_history_node().undo_group)
|
2016-07-20 21:20:03 +02:00
|
|
|
apply_modification(modification);
|
|
|
|
}
|
2016-07-20 10:41:45 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-03-30 13:37:18 +02:00
|
|
|
void Buffer::check_invariant() const
|
|
|
|
{
|
2013-02-27 19:02:01 +01:00
|
|
|
#ifdef KAK_DEBUG
|
2013-04-09 20:04:11 +02:00
|
|
|
kak_assert(not m_lines.empty());
|
2012-03-30 13:37:18 +02:00
|
|
|
for (auto& line : m_lines)
|
|
|
|
{
|
2015-01-19 20:31:56 +01:00
|
|
|
kak_assert(line->strview().length() > 0);
|
|
|
|
kak_assert(line->strview().back() == '\n');
|
2012-03-30 13:37:18 +02:00
|
|
|
}
|
2013-02-27 19:02:01 +01:00
|
|
|
#endif
|
2012-03-30 13:37:18 +02:00
|
|
|
}
|
|
|
|
|
2019-12-18 01:36:17 +01:00
|
|
|
BufferRange Buffer::do_insert(BufferCoord pos, StringView content)
|
2012-02-22 22:54:25 +01:00
|
|
|
{
|
2013-05-22 19:21:59 +02:00
|
|
|
kak_assert(is_valid(pos));
|
2013-03-18 19:39:32 +01:00
|
|
|
|
|
|
|
if (content.empty())
|
2019-12-18 01:36:17 +01:00
|
|
|
return {pos, pos};
|
2013-03-18 19:39:32 +01:00
|
|
|
|
2016-03-16 00:06:49 +01:00
|
|
|
const bool at_end = is_end(pos);
|
2016-03-16 14:48:11 +01:00
|
|
|
const bool append_lines = at_end and (m_lines.empty() or byte_at(back_coord()) == '\n');
|
2012-04-04 15:56:19 +02:00
|
|
|
|
2016-03-17 13:07:20 +01:00
|
|
|
const StringView prefix = append_lines ?
|
2016-03-16 00:28:10 +01:00
|
|
|
StringView{} : m_lines[pos.line].substr(0, pos.column);
|
|
|
|
const StringView suffix = at_end ?
|
|
|
|
StringView{} : m_lines[pos.line].substr(pos.column);
|
2012-03-30 13:37:18 +02:00
|
|
|
|
2016-03-16 00:28:10 +01:00
|
|
|
LineList new_lines;
|
|
|
|
ByteCount start = 0;
|
|
|
|
for (ByteCount i = 0; i < content.length(); ++i)
|
|
|
|
{
|
|
|
|
if (content[i] == '\n')
|
2012-02-22 22:54:25 +01:00
|
|
|
{
|
2016-03-16 00:28:10 +01:00
|
|
|
StringView line = content.substr(start, i + 1 - start);
|
2022-05-18 11:58:37 +02:00
|
|
|
new_lines.push_back(start == 0 ? StringData::create({prefix, line}) : StringData::create({line}));
|
2016-03-16 00:28:10 +01:00
|
|
|
start = i + 1;
|
2012-02-22 22:54:25 +01:00
|
|
|
}
|
|
|
|
}
|
2016-03-16 00:28:10 +01:00
|
|
|
if (start == 0)
|
2017-01-30 13:05:04 +01:00
|
|
|
new_lines.push_back(StringData::create({prefix, content, suffix}));
|
2016-03-16 00:28:10 +01:00
|
|
|
else if (start != content.length() or not suffix.empty())
|
2017-01-30 13:05:04 +01:00
|
|
|
new_lines.push_back(StringData::create({content.substr(start), suffix}));
|
2016-03-16 00:28:10 +01:00
|
|
|
|
|
|
|
auto line_it = m_lines.begin() + (int)pos.line;
|
|
|
|
auto new_lines_it = new_lines.begin();
|
2017-01-28 14:04:55 +01:00
|
|
|
if (not append_lines) // replace first line with new first line
|
2016-03-16 00:28:10 +01:00
|
|
|
*line_it++ = std::move(*new_lines_it++);
|
|
|
|
|
|
|
|
m_lines.insert(line_it,
|
|
|
|
std::make_move_iterator(new_lines_it),
|
|
|
|
std::make_move_iterator(new_lines.end()));
|
|
|
|
|
|
|
|
const LineCount last_line = pos.line + new_lines.size() - 1;
|
2017-01-01 16:33:46 +01:00
|
|
|
const auto end = at_end ? line_count()
|
|
|
|
: BufferCoord{ last_line, m_lines[last_line].length() - suffix.length() };
|
2012-03-30 13:37:18 +02:00
|
|
|
|
2017-06-11 13:01:40 +02:00
|
|
|
m_changes.push_back({ Change::Insert, pos, end });
|
2019-12-18 01:36:17 +01:00
|
|
|
return {pos, end};
|
2012-03-30 13:37:18 +02:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferCoord Buffer::do_erase(BufferCoord begin, BufferCoord end)
|
2012-03-30 13:37:18 +02:00
|
|
|
{
|
2017-04-20 18:31:27 +02:00
|
|
|
if (begin == end)
|
|
|
|
return begin;
|
|
|
|
|
2013-05-22 19:21:59 +02:00
|
|
|
kak_assert(is_valid(begin));
|
|
|
|
kak_assert(is_valid(end));
|
2014-06-26 20:01:39 +02:00
|
|
|
StringView prefix = m_lines[begin.line].substr(0, begin.column);
|
2017-06-11 13:01:40 +02:00
|
|
|
StringView suffix = end.line == line_count() ? StringView{} : m_lines[end.line].substr(end.column);
|
2012-03-30 13:37:18 +02:00
|
|
|
|
2022-05-18 11:58:37 +02:00
|
|
|
auto new_line = (not prefix.empty() or not suffix.empty()) ? StringData::create({prefix, suffix}) : StringDataPtr{};
|
|
|
|
m_lines.erase(m_lines.begin() + (int)begin.line, m_lines.begin() + (int)end.line);
|
2012-03-30 13:37:18 +02:00
|
|
|
|
2017-06-11 13:01:40 +02:00
|
|
|
m_changes.push_back({ Change::Erase, begin, end });
|
2022-05-18 11:58:37 +02:00
|
|
|
if (new_line)
|
|
|
|
m_lines.get_storage(begin.line) = std::move(new_line);
|
|
|
|
|
|
|
|
return begin;
|
2012-02-22 22:54:25 +01:00
|
|
|
}
|
|
|
|
|
2011-12-06 19:58:43 +01:00
|
|
|
void Buffer::apply_modification(const Modification& modification)
|
2011-09-06 20:49:32 +02:00
|
|
|
{
|
2016-02-05 01:20:45 +01:00
|
|
|
StringView content = modification.content->strview();
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferCoord coord = modification.coord;
|
2012-03-30 13:37:18 +02:00
|
|
|
|
2013-05-22 19:21:59 +02:00
|
|
|
kak_assert(is_valid(coord));
|
2011-09-06 20:49:32 +02:00
|
|
|
switch (modification.type)
|
|
|
|
{
|
2011-12-06 19:58:43 +01:00
|
|
|
case Modification::Insert:
|
2013-05-22 19:21:59 +02:00
|
|
|
do_insert(coord, content);
|
2011-09-06 20:49:32 +02:00
|
|
|
break;
|
2011-12-06 19:58:43 +01:00
|
|
|
case Modification::Erase:
|
2011-09-06 20:49:32 +02:00
|
|
|
{
|
2016-03-17 13:36:49 +01:00
|
|
|
auto end = advance(coord, content.length());
|
2013-05-23 14:17:25 +02:00
|
|
|
kak_assert(string(coord, end) == content);
|
2013-05-22 19:21:59 +02:00
|
|
|
do_erase(coord, end);
|
2011-09-06 20:49:32 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
2013-04-09 20:04:11 +02:00
|
|
|
kak_assert(false);
|
2011-09-06 20:49:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-18 01:36:17 +01:00
|
|
|
BufferRange Buffer::insert(BufferCoord pos, StringView content)
|
2012-08-10 19:12:43 +02:00
|
|
|
{
|
2018-02-11 03:06:19 +01:00
|
|
|
throw_if_read_only();
|
|
|
|
|
2016-03-16 14:59:30 +01:00
|
|
|
kak_assert(is_valid(pos));
|
2012-08-10 19:12:43 +02:00
|
|
|
if (content.empty())
|
2019-12-18 01:36:17 +01:00
|
|
|
return {pos, pos};
|
2012-08-14 14:13:10 +02:00
|
|
|
|
2016-02-05 01:20:45 +01:00
|
|
|
StringDataPtr real_content;
|
2016-03-16 14:59:30 +01:00
|
|
|
if (is_end(pos) and content.back() != '\n')
|
2015-01-15 14:54:38 +01:00
|
|
|
real_content = intern(content + "\n");
|
2014-10-03 14:39:13 +02:00
|
|
|
else
|
2015-01-15 14:54:38 +01:00
|
|
|
real_content = intern(content);
|
2012-08-14 14:13:10 +02:00
|
|
|
|
2012-11-21 13:43:10 +01:00
|
|
|
if (not (m_flags & Flags::NoUndo))
|
2022-02-21 22:28:33 +01:00
|
|
|
m_current_undo_group.push_back({Modification::Insert, pos, real_content});
|
2016-03-16 14:59:30 +01:00
|
|
|
return do_insert(pos, real_content->strview());
|
2012-08-10 19:12:43 +02:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferCoord Buffer::erase(BufferCoord begin, BufferCoord end)
|
2011-09-06 20:49:32 +02:00
|
|
|
{
|
2018-02-11 03:06:19 +01:00
|
|
|
throw_if_read_only();
|
|
|
|
|
2016-03-16 14:59:30 +01:00
|
|
|
kak_assert(is_valid(begin) and is_valid(end));
|
|
|
|
// do not erase last \n except if we erase from the start of a line, and normalize
|
|
|
|
// end coord
|
|
|
|
if (is_end(end))
|
2016-09-22 21:36:26 +02:00
|
|
|
end = (begin.column != 0 or begin == BufferCoord{0,0}) ? prev(end) : end_coord();
|
2012-08-14 14:13:10 +02:00
|
|
|
|
2016-03-16 14:59:30 +01:00
|
|
|
if (begin >= end) // use >= to handle case where begin is {line_count}
|
2013-06-06 19:39:53 +02:00
|
|
|
return begin;
|
2012-03-12 22:31:27 +01:00
|
|
|
|
2012-11-21 13:43:10 +01:00
|
|
|
if (not (m_flags & Flags::NoUndo))
|
2017-09-01 12:31:37 +02:00
|
|
|
m_current_undo_group.push_back({Modification::Erase, begin,
|
|
|
|
intern(string(begin, end))});
|
2016-03-16 14:59:30 +01:00
|
|
|
return do_erase(begin, end);
|
2011-09-06 20:49:32 +02:00
|
|
|
}
|
|
|
|
|
2019-12-18 01:36:17 +01:00
|
|
|
BufferRange Buffer::replace(BufferCoord begin, BufferCoord end, StringView content)
|
2016-03-16 14:48:11 +01:00
|
|
|
{
|
2018-02-11 03:06:19 +01:00
|
|
|
throw_if_read_only();
|
|
|
|
|
2019-12-18 01:36:17 +01:00
|
|
|
if (std::equal(iterator_at(begin), iterator_at(end), content.begin(), content.end()))
|
|
|
|
return {begin, end};
|
|
|
|
|
2017-06-11 13:01:40 +02:00
|
|
|
if (is_end(end) and not content.empty() and content.back() == '\n')
|
2017-06-26 17:16:46 +02:00
|
|
|
{
|
2019-12-18 01:36:17 +01:00
|
|
|
auto pos = insert(erase(begin, back_coord()),
|
|
|
|
content.substr(0, content.length() - 1)).begin;
|
|
|
|
return {pos, end_coord()};
|
2017-06-26 17:16:46 +02:00
|
|
|
}
|
2019-12-18 01:36:17 +01:00
|
|
|
return insert(erase(begin, end), content);
|
2016-03-16 14:48:11 +01:00
|
|
|
}
|
|
|
|
|
2011-10-05 16:21:24 +02:00
|
|
|
bool Buffer::is_modified() const
|
|
|
|
{
|
2017-06-28 07:48:24 +02:00
|
|
|
return m_flags & Flags::File and
|
2018-05-02 13:31:44 +02:00
|
|
|
(m_history_id != m_last_save_history_id or
|
2017-06-28 07:48:24 +02:00
|
|
|
not m_current_undo_group.empty());
|
2011-10-05 16:21:24 +02:00
|
|
|
}
|
|
|
|
|
2019-12-19 03:39:30 +01:00
|
|
|
void Buffer::notify_saved(FsStatus status)
|
2011-10-05 16:21:24 +02:00
|
|
|
{
|
2012-12-19 18:56:20 +01:00
|
|
|
if (not m_current_undo_group.empty())
|
2013-02-20 14:20:16 +01:00
|
|
|
commit_undo_group();
|
2012-12-19 18:56:20 +01:00
|
|
|
|
2012-12-28 13:52:19 +01:00
|
|
|
m_flags &= ~Flags::New;
|
2018-05-02 13:31:44 +02:00
|
|
|
m_last_save_history_id = m_history_id;
|
2019-12-19 03:39:30 +01:00
|
|
|
m_fs_status = status;
|
2011-10-05 16:21:24 +02:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferCoord Buffer::advance(BufferCoord coord, ByteCount count) const
|
2013-05-22 18:59:55 +02:00
|
|
|
{
|
2014-05-24 18:08:01 +02:00
|
|
|
if (count > 0)
|
|
|
|
{
|
|
|
|
auto line = coord.line;
|
|
|
|
count += coord.column;
|
|
|
|
while (count >= m_lines[line].length())
|
|
|
|
{
|
|
|
|
count -= m_lines[line++].length();
|
|
|
|
if (line == line_count())
|
|
|
|
return end_coord();
|
|
|
|
}
|
|
|
|
return { line, count };
|
|
|
|
}
|
|
|
|
else if (count < 0)
|
|
|
|
{
|
|
|
|
auto line = coord.line;
|
|
|
|
count += coord.column;
|
|
|
|
while (count < 0)
|
|
|
|
{
|
2022-12-22 23:34:42 +01:00
|
|
|
if (--line < 0)
|
2014-05-24 18:08:01 +02:00
|
|
|
return {0, 0};
|
2022-12-22 23:34:42 +01:00
|
|
|
count += m_lines[line].length();
|
2014-05-24 18:08:01 +02:00
|
|
|
}
|
|
|
|
return { line, count };
|
|
|
|
}
|
|
|
|
return coord;
|
2013-05-22 18:59:55 +02:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferCoord Buffer::char_next(BufferCoord coord) const
|
2013-06-03 18:56:48 +02:00
|
|
|
{
|
|
|
|
if (coord.column < m_lines[coord.line].length() - 1)
|
|
|
|
{
|
2014-10-03 14:39:13 +02:00
|
|
|
auto line = m_lines[coord.line];
|
2017-06-12 07:10:18 +02:00
|
|
|
auto column = coord.column + utf8::codepoint_size(line[coord.column]);
|
|
|
|
if (column >= line.length()) // Handle invalid utf-8
|
|
|
|
return { coord.line + 1, 0 };
|
|
|
|
return { coord.line, column };
|
2013-06-03 18:56:48 +02:00
|
|
|
}
|
2017-06-12 07:10:18 +02:00
|
|
|
return { coord.line + 1, 0 };
|
2013-06-03 18:56:48 +02:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
BufferCoord Buffer::char_prev(BufferCoord coord) const
|
2013-06-03 18:56:48 +02:00
|
|
|
{
|
|
|
|
kak_assert(is_valid(coord));
|
2017-06-11 13:01:40 +02:00
|
|
|
if (coord.column == 0)
|
2017-06-12 07:10:18 +02:00
|
|
|
return { coord.line - 1, m_lines[coord.line - 1].length() - 1 };
|
|
|
|
|
|
|
|
auto line = m_lines[coord.line];
|
2022-12-15 03:29:45 +01:00
|
|
|
auto column = (int)(utf8::character_start(line.begin() + coord.column - 1, line.begin()) - line.begin());
|
2017-06-12 07:10:18 +02:00
|
|
|
return { coord.line, column };
|
2013-06-03 18:56:48 +02:00
|
|
|
}
|
|
|
|
|
2019-11-23 15:55:46 +01:00
|
|
|
void Buffer::set_fs_status(FsStatus status)
|
2013-10-15 19:51:31 +02:00
|
|
|
{
|
|
|
|
kak_assert(m_flags & Flags::File);
|
2019-11-23 15:55:46 +01:00
|
|
|
m_fs_status = std::move(status);
|
2013-10-15 19:51:31 +02:00
|
|
|
}
|
|
|
|
|
2019-11-23 15:55:46 +01:00
|
|
|
const FsStatus& Buffer::fs_status() const
|
2013-10-15 19:51:31 +02:00
|
|
|
{
|
|
|
|
kak_assert(m_flags & Flags::File);
|
2019-11-23 15:55:46 +01:00
|
|
|
return m_fs_status;
|
2013-10-15 19:51:31 +02:00
|
|
|
}
|
|
|
|
|
2013-11-12 21:36:42 +01:00
|
|
|
void Buffer::on_option_changed(const Option& option)
|
|
|
|
{
|
2016-07-20 19:45:50 +02:00
|
|
|
if (option.name() == "readonly")
|
|
|
|
{
|
|
|
|
if (option.get<bool>())
|
|
|
|
m_flags |= Flags::ReadOnly;
|
|
|
|
else
|
|
|
|
m_flags &= ~Flags::ReadOnly;
|
|
|
|
}
|
2018-10-22 23:15:53 +02:00
|
|
|
run_hook_in_own_context(Hook::BufSetOption,
|
2020-06-07 05:46:50 +02:00
|
|
|
format("{}={}", option.name(), option.get_desc_string()));
|
2013-12-11 14:57:10 +01:00
|
|
|
}
|
|
|
|
|
2018-10-22 23:15:53 +02:00
|
|
|
void Buffer::run_hook_in_own_context(Hook hook, StringView param, String client_name)
|
2013-12-11 14:57:10 +01:00
|
|
|
{
|
2016-11-14 14:59:33 +01:00
|
|
|
if (m_flags & Buffer::Flags::NoHooks)
|
|
|
|
return;
|
|
|
|
|
2016-11-24 14:35:42 +01:00
|
|
|
InputHandler hook_handler{{ *this, Selection{} },
|
2018-05-13 09:59:01 +02:00
|
|
|
Context::Flags::Draft,
|
2016-11-24 14:35:42 +01:00
|
|
|
std::move(client_name)};
|
2018-10-22 23:15:53 +02:00
|
|
|
hooks().run_hook(hook, param, hook_handler.context());
|
2013-11-12 21:36:42 +01:00
|
|
|
}
|
2013-12-11 14:57:10 +01:00
|
|
|
|
2018-03-13 04:00:57 +01:00
|
|
|
Optional<BufferCoord> Buffer::last_modification_coord() const
|
2014-04-08 00:39:12 +02:00
|
|
|
{
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
if (m_history_id == 0)
|
2014-04-08 00:39:12 +02:00
|
|
|
return {};
|
2018-05-02 13:31:44 +02:00
|
|
|
return current_history_node().undo_group.back().coord;
|
2014-04-08 00:39:12 +02:00
|
|
|
}
|
|
|
|
|
2014-09-22 20:19:34 +02:00
|
|
|
String Buffer::debug_description() const
|
|
|
|
{
|
|
|
|
size_t content_size = 0;
|
|
|
|
for (auto& line : m_lines)
|
2015-01-19 20:31:56 +01:00
|
|
|
content_size += (int)line->strview().length();
|
2014-09-22 20:19:34 +02:00
|
|
|
|
2018-05-02 13:31:44 +02:00
|
|
|
const size_t additional_size = accumulate(m_history, 0, [](size_t s, auto&& history) {
|
|
|
|
return sizeof(history) + history.undo_group.size() * sizeof(Modification) + s;
|
|
|
|
}) + m_changes.size() * sizeof(Change);
|
2014-09-22 20:19:34 +02:00
|
|
|
|
2019-03-27 19:30:41 +01:00
|
|
|
return format("{}\nFlags: {}{}{}{}{}{}{}{}\nUsed mem: content={} additional={}\n",
|
2015-03-31 00:06:02 +02:00
|
|
|
display_name(),
|
|
|
|
(m_flags & Flags::File) ? "File (" + name() + ") " : "",
|
|
|
|
(m_flags & Flags::New) ? "New " : "",
|
|
|
|
(m_flags & Flags::Fifo) ? "Fifo " : "",
|
|
|
|
(m_flags & Flags::NoUndo) ? "NoUndo " : "",
|
2019-03-27 19:30:41 +01:00
|
|
|
(m_flags & Flags::NoHooks) ? "NoHooks " : "",
|
|
|
|
(m_flags & Flags::Debug) ? "Debug " : "",
|
|
|
|
(m_flags & Flags::ReadOnly) ? "ReadOnly " : "",
|
|
|
|
is_modified() ? "Modified " : "",
|
2015-03-31 00:06:02 +02:00
|
|
|
content_size, additional_size);
|
2014-09-22 20:19:34 +02:00
|
|
|
}
|
|
|
|
|
2015-05-22 14:58:56 +02:00
|
|
|
UnitTest test_buffer{[]()
|
|
|
|
{
|
2021-05-28 09:03:06 +02:00
|
|
|
auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create({lines})...}; };
|
2015-05-22 14:58:56 +02:00
|
|
|
|
2021-05-28 09:03:06 +02:00
|
|
|
Buffer empty_buffer("empty", Buffer::Flags::None, make_lines("\n"));
|
|
|
|
|
|
|
|
Buffer buffer("test", Buffer::Flags::None, make_lines("allo ?\n", "mais que fais la police\n", " hein ?\n", " youpi\n"));
|
2015-05-22 14:58:56 +02:00
|
|
|
kak_assert(buffer.line_count() == 4);
|
|
|
|
|
|
|
|
BufferIterator pos = buffer.begin();
|
|
|
|
kak_assert(*pos == 'a');
|
|
|
|
pos += 6;
|
2016-09-22 21:36:26 +02:00
|
|
|
kak_assert(pos.coord() == BufferCoord{0, 6});
|
2015-05-22 14:58:56 +02:00
|
|
|
++pos;
|
2016-09-22 21:36:26 +02:00
|
|
|
kak_assert(pos.coord() == BufferCoord{1, 0});
|
2015-05-22 14:58:56 +02:00
|
|
|
--pos;
|
2016-09-22 21:36:26 +02:00
|
|
|
kak_assert(pos.coord() == BufferCoord{0, 6});
|
2015-05-22 14:58:56 +02:00
|
|
|
pos += 1;
|
2016-09-22 21:36:26 +02:00
|
|
|
kak_assert(pos.coord() == BufferCoord{1, 0});
|
2016-03-16 14:59:30 +01:00
|
|
|
buffer.insert(pos.coord(), "tchou kanaky\n");
|
2015-05-22 14:58:56 +02:00
|
|
|
kak_assert(buffer.line_count() == 5);
|
|
|
|
BufferIterator pos2 = buffer.end();
|
|
|
|
pos2 -= 9;
|
|
|
|
kak_assert(*pos2 == '?');
|
|
|
|
|
|
|
|
String str = buffer.string({ 4, 1 }, buffer.next({ 4, 5 }));
|
|
|
|
kak_assert(str == "youpi");
|
|
|
|
|
|
|
|
// check insert at end behaviour: auto add end of line if necessary
|
|
|
|
pos = buffer.end()-1;
|
2016-03-16 14:59:30 +01:00
|
|
|
buffer.insert(pos.coord(), "tchou");
|
2018-01-17 23:00:54 +01:00
|
|
|
kak_assert(buffer.string(pos.coord(), buffer.end_coord()) == "tchou\n"_sv);
|
2015-05-22 14:58:56 +02:00
|
|
|
|
|
|
|
pos = buffer.end()-1;
|
2016-03-16 14:59:30 +01:00
|
|
|
buffer.insert(buffer.end_coord(), "kanaky\n");
|
2018-01-17 23:00:54 +01:00
|
|
|
kak_assert(buffer.string((pos+1).coord(), buffer.end_coord()) == "kanaky\n"_sv);
|
2015-05-22 14:58:56 +02:00
|
|
|
|
|
|
|
buffer.commit_undo_group();
|
2016-03-16 14:59:30 +01:00
|
|
|
buffer.erase((pos+1).coord(), buffer.end_coord());
|
|
|
|
buffer.insert(buffer.end_coord(), "mutch\n");
|
2015-05-22 14:58:56 +02:00
|
|
|
buffer.commit_undo_group();
|
|
|
|
buffer.undo();
|
2018-01-17 23:00:54 +01:00
|
|
|
kak_assert(buffer.string(buffer.advance(buffer.end_coord(), -7), buffer.end_coord()) == "kanaky\n"_sv);
|
2015-05-22 14:58:56 +02:00
|
|
|
buffer.redo();
|
2018-01-17 23:00:54 +01:00
|
|
|
kak_assert(buffer.string(buffer.advance(buffer.end_coord(), -6), buffer.end_coord()) == "mutch\n"_sv);
|
2015-05-22 14:58:56 +02:00
|
|
|
}};
|
|
|
|
|
|
|
|
UnitTest test_undo{[]()
|
|
|
|
{
|
2021-05-28 09:03:06 +02:00
|
|
|
auto make_lines = [](auto&&... lines) { return BufferLines{StringData::create({lines})...}; };
|
|
|
|
|
|
|
|
Buffer buffer("test", Buffer::Flags::None, make_lines("allo ?\n", "mais que fais la police\n", " hein ?\n", " youpi\n"));
|
2019-01-23 09:48:43 +01:00
|
|
|
auto pos = buffer.end_coord();
|
|
|
|
buffer.insert(pos, "kanaky\n"); // change 1
|
2016-07-20 10:41:45 +02:00
|
|
|
buffer.commit_undo_group();
|
|
|
|
buffer.erase(pos, buffer.end_coord()); // change 2
|
|
|
|
buffer.commit_undo_group();
|
|
|
|
buffer.insert(2_line, "tchou\n"); // change 3
|
|
|
|
buffer.commit_undo_group();
|
|
|
|
buffer.undo();
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
buffer.insert(2_line, "mutch\n"); // change 4 (inverse of 3) and 5
|
2016-07-20 10:41:45 +02:00
|
|
|
buffer.commit_undo_group();
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
buffer.erase({2, 1}, {2, 5}); // change 6
|
2016-07-20 10:41:45 +02:00
|
|
|
buffer.commit_undo_group();
|
2016-07-20 21:20:03 +02:00
|
|
|
buffer.undo(2);
|
|
|
|
buffer.redo(2);
|
2015-05-22 14:58:56 +02:00
|
|
|
buffer.undo();
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
buffer.replace(2_line, buffer.end_coord(), "foo"); // change 7 (inverse of 6) and 8
|
2016-07-20 10:41:45 +02:00
|
|
|
buffer.commit_undo_group();
|
|
|
|
|
Switch undo storage from a tree to a plain list
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
2023-04-17 10:25:51 +02:00
|
|
|
kak_assert(buffer.history().size() == 9);
|
|
|
|
|
|
|
|
auto check_content = [&](const Vector<StringView>& lines) {
|
|
|
|
kak_assert(buffer.line_count() == lines.size());
|
|
|
|
for (size_t i = 0; i < lines.size(); ++i)
|
|
|
|
kak_assert(buffer[i] == lines[i] + "\n");
|
|
|
|
};
|
|
|
|
|
|
|
|
kak_assert(not buffer.redo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", "foo" , });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", "mutch" , " hein ?", " youpi", });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", "m" , " hein ?", " youpi", });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", "mutch" , " hein ?", " youpi", });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", " hein ?", " youpi" , });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", "tchou" , " hein ?", " youpi", });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", " hein ?", " youpi" , });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", " hein ?", " youpi" , "kanaky", });
|
|
|
|
kak_assert(buffer.undo(1));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", " hein ?", " youpi" , });
|
|
|
|
kak_assert(not buffer.undo(1));
|
|
|
|
|
|
|
|
kak_assert(buffer.redo(1000));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", "foo" , });
|
|
|
|
|
|
|
|
kak_assert(buffer.undo(1000));
|
|
|
|
check_content({ "allo ?", "mais que fais la police", " hein ?", " youpi" , });
|
2015-05-22 14:58:56 +02:00
|
|
|
}};
|
|
|
|
|
2011-09-02 18:51:20 +02:00
|
|
|
}
|