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
|
|
|
|
{
|
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
Buffer::HistoryNode::HistoryNode(HistoryId parent)
|
|
|
|
: parent{parent}, 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},
|
2023-06-17 09:30:43 +02:00
|
|
|
m_history{{HistoryId::Invalid}},
|
|
|
|
m_history_id{HistoryId::First},
|
|
|
|
m_last_save_history_id{HistoryId::First},
|
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;
|
2023-06-17 09:30:43 +02:00
|
|
|
m_last_save_history_id = HistoryId::Invalid;
|
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
|
2023-06-17 09:30:43 +02:00
|
|
|
m_history_id = HistoryId::First;
|
|
|
|
m_last_save_history_id = HistoryId::First;
|
|
|
|
m_history = {HistoryNode{HistoryId::Invalid}};
|
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();
|
2023-06-21 09:36:33 +02:00
|
|
|
for (auto& [op, len] : diff)
|
2015-05-13 21:54:44 +02:00
|
|
|
{
|
2023-06-21 09:36:33 +02:00
|
|
|
if (op == DiffOp::Keep)
|
2020-03-20 10:26:05 +01:00
|
|
|
{
|
2023-06-21 09:36:33 +02:00
|
|
|
it += len;
|
|
|
|
new_it += len;
|
2020-03-20 10:26:05 +01:00
|
|
|
}
|
2023-06-21 09:36:33 +02:00
|
|
|
else if (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
|
|
|
|
2023-06-21 09:36:33 +02:00
|
|
|
for (LineCount line = 0; line < 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
|
|
|
|
2023-06-21 09:36:33 +02:00
|
|
|
m_changes.push_back({Change::Insert, cur_line, cur_line + len});
|
|
|
|
m_lines.insert(it, new_it, new_it + len);
|
|
|
|
it = m_lines.begin() + (int)(cur_line + len);
|
|
|
|
new_it += len;
|
2015-05-13 21:54:44 +02:00
|
|
|
}
|
2023-06-21 09:36:33 +02:00
|
|
|
else if (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
|
|
|
|
2023-06-21 09:36:33 +02:00
|
|
|
for (LineCount line = 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
|
|
|
|
2023-06-21 09:36:33 +02:00
|
|
|
it = m_lines.erase(it, it + len);
|
|
|
|
m_changes.push_back({ Change::Erase, cur_line, cur_line + 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;
|
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
const HistoryId id = next_history_id();
|
|
|
|
m_history.push_back({m_history_id});
|
2018-05-02 13:31:44 +02:00
|
|
|
m_history.back().undo_group = std::move(m_current_undo_group);
|
2013-04-25 14:03:55 +02:00
|
|
|
m_current_undo_group.clear();
|
2023-06-17 09:30:43 +02:00
|
|
|
current_history_node().redo_child = id;
|
|
|
|
m_history_id = id;
|
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();
|
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
if (current_history_node().parent == HistoryId::Invalid)
|
2011-09-06 20:49:32 +02:00
|
|
|
return false;
|
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
while (count-- != 0 and current_history_node().parent != HistoryId::Invalid)
|
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());
|
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
m_history_id = current_history_node().parent;
|
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();
|
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
if (current_history_node().redo_child == HistoryId::Invalid)
|
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
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
while (count-- != 0 and current_history_node().redo_child != HistoryId::Invalid)
|
2016-07-20 21:20:03 +02:00
|
|
|
{
|
2023-06-17 09:30:43 +02:00
|
|
|
m_history_id = current_history_node().redo_child;
|
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);
|
|
|
|
}
|
2023-06-17 09:30:43 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Buffer::move_to(HistoryId id)
|
|
|
|
{
|
|
|
|
if (id >= next_history_id())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
throw_if_read_only();
|
|
|
|
|
|
|
|
commit_undo_group();
|
|
|
|
|
|
|
|
auto find_lowest_common_parent = [this](HistoryId a, HistoryId b) {
|
|
|
|
auto depth_of = [this](HistoryId id) {
|
|
|
|
size_t depth = 0;
|
|
|
|
for (; history_node(id).parent != HistoryId::Invalid; id = history_node(id).parent)
|
|
|
|
++depth;
|
|
|
|
return depth;
|
|
|
|
};
|
|
|
|
auto depthA = depth_of(a), depthB = depth_of(b);
|
|
|
|
|
|
|
|
for (; depthA > depthB; --depthA)
|
|
|
|
a = history_node(a).parent;
|
|
|
|
for (; depthB > depthA; --depthB)
|
|
|
|
b = history_node(b).parent;
|
|
|
|
|
|
|
|
while (a != b)
|
|
|
|
{
|
|
|
|
a = history_node(a).parent;
|
|
|
|
b = history_node(b).parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
kak_assert(a == b and a != HistoryId::Invalid);
|
|
|
|
return a;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto parent = find_lowest_common_parent(m_history_id, id);
|
|
|
|
|
|
|
|
// undo up to common parent
|
|
|
|
for (auto id = m_history_id; id != parent; id = history_node(id).parent)
|
|
|
|
{
|
|
|
|
for (const Modification& modification : history_node(id).undo_group | reverse())
|
|
|
|
apply_modification(modification.inverse());
|
|
|
|
}
|
|
|
|
|
|
|
|
static void (*apply_from_parent)(Buffer&, HistoryId, HistoryId) =
|
|
|
|
[](Buffer& buffer, HistoryId parent, HistoryId id) {
|
|
|
|
if (id == parent)
|
|
|
|
return;
|
2016-07-20 10:41:45 +02:00
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
auto& node = buffer.history_node(id);
|
|
|
|
apply_from_parent(buffer, parent, node.parent);
|
|
|
|
|
|
|
|
buffer.history_node(node.parent).redo_child = id;
|
|
|
|
|
|
|
|
for (const Modification& modification : node.undo_group)
|
|
|
|
buffer.apply_modification(modification);
|
|
|
|
};
|
|
|
|
|
|
|
|
apply_from_parent(*this, parent, id);
|
|
|
|
m_history_id = id;
|
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
|
|
|
{
|
2023-06-17 09:30:43 +02:00
|
|
|
if (m_history_id == HistoryId::First)
|
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();
|
2023-06-17 09:30:43 +02:00
|
|
|
buffer.insert(2_line, "mutch\n"); // change 4
|
2016-07-20 10:41:45 +02:00
|
|
|
buffer.commit_undo_group();
|
2023-06-17 09:30:43 +02:00
|
|
|
buffer.erase({2, 1}, {2, 5}); // change 5
|
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();
|
2023-06-17 09:30:43 +02:00
|
|
|
buffer.replace(2_line, buffer.end_coord(), "foo"); // change 6
|
2016-07-20 10:41:45 +02:00
|
|
|
buffer.commit_undo_group();
|
|
|
|
|
2023-06-17 09:30:43 +02:00
|
|
|
kak_assert((int)buffer.line_count() == 3);
|
|
|
|
kak_assert(buffer[0_line] == "allo ?\n");
|
|
|
|
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
|
|
|
kak_assert(buffer[2_line] == "foo\n");
|
|
|
|
|
|
|
|
buffer.move_to((Buffer::HistoryId)3);
|
|
|
|
kak_assert((int)buffer.line_count() == 5);
|
|
|
|
kak_assert(buffer[0_line] == "allo ?\n");
|
|
|
|
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
|
|
|
kak_assert(buffer[2_line] == "tchou\n");
|
|
|
|
kak_assert(buffer[3_line] == " hein ?\n");
|
|
|
|
kak_assert(buffer[4_line] == " youpi\n");
|
|
|
|
|
|
|
|
buffer.move_to((Buffer::HistoryId)4);
|
|
|
|
kak_assert((int)buffer.line_count() == 5);
|
|
|
|
kak_assert(buffer[0_line] == "allo ?\n");
|
|
|
|
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
|
|
|
kak_assert(buffer[2_line] == "mutch\n");
|
|
|
|
kak_assert(buffer[3_line] == " hein ?\n");
|
|
|
|
kak_assert(buffer[4_line] == " youpi\n");
|
|
|
|
|
|
|
|
buffer.move_to(Buffer::HistoryId::First);
|
|
|
|
kak_assert((int)buffer.line_count() == 4);
|
|
|
|
kak_assert(buffer[0_line] == "allo ?\n");
|
|
|
|
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
|
|
|
kak_assert(buffer[2_line] == " hein ?\n");
|
|
|
|
kak_assert(buffer[3_line] == " youpi\n");
|
|
|
|
kak_assert(not buffer.undo());
|
|
|
|
|
|
|
|
buffer.move_to((Buffer::HistoryId)5);
|
|
|
|
kak_assert(not buffer.redo());
|
|
|
|
|
|
|
|
buffer.move_to((Buffer::HistoryId)6);
|
|
|
|
kak_assert(not buffer.redo());
|
2015-05-22 14:58:56 +02:00
|
|
|
}};
|
|
|
|
|
2011-09-02 18:51:20 +02:00
|
|
|
}
|