PieceTable: newline test from the line index, memchr rebuild; remove dead code
GetLine and LineColToByteOffset fetched each line's last byte (a piece lookup, an allocation and a cache write) to see whether it was a newline; the line index already says so: every line but the last ends at one. LineColToByteOffset runs on every insert. RebuildLineIndex scans with memchr instead of byte by byte. Removed, having no callers: OptimizedSearch (built only into its own test), UndoNodePool.h, SwapManager::RecordSplit/RecordJoin (replay still reads SPLIT/JOIN), PieceTable::Reserve/Capacity and its capacity constructor, Buffer's write-only render column, Editor's mode, no-kill, dirtyex and repeatable flags and the UCount getter, and RetryPolicy::Conservative. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
@@ -347,7 +347,6 @@ Buffer::Buffer(const Buffer &other)
|
||||
{
|
||||
curx_ = other.curx_;
|
||||
cury_ = other.cury_;
|
||||
rx_ = other.rx_;
|
||||
nrows_ = other.nrows_;
|
||||
rowoffs_ = other.rowoffs_;
|
||||
coloffs_ = other.coloffs_;
|
||||
@@ -401,7 +400,6 @@ Buffer::operator=(const Buffer &other)
|
||||
return *this;
|
||||
curx_ = other.curx_;
|
||||
cury_ = other.cury_;
|
||||
rx_ = other.rx_;
|
||||
nrows_ = other.nrows_;
|
||||
rowoffs_ = other.rowoffs_;
|
||||
coloffs_ = other.coloffs_;
|
||||
@@ -450,7 +448,6 @@ Buffer::operator=(const Buffer &other)
|
||||
Buffer::Buffer(Buffer &&other) noexcept
|
||||
: curx_(other.curx_),
|
||||
cury_(other.cury_),
|
||||
rx_(other.rx_),
|
||||
nrows_(other.nrows_),
|
||||
rowoffs_(other.rowoffs_),
|
||||
coloffs_(other.coloffs_),
|
||||
@@ -502,7 +499,6 @@ Buffer::operator=(Buffer &&other) noexcept
|
||||
|
||||
curx_ = other.curx_;
|
||||
cury_ = other.cury_;
|
||||
rx_ = other.rx_;
|
||||
nrows_ = other.nrows_;
|
||||
rowoffs_ = other.rowoffs_;
|
||||
coloffs_ = other.coloffs_;
|
||||
@@ -593,7 +589,7 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
||||
dirty_ = false;
|
||||
|
||||
// Reset cursor/viewport state
|
||||
curx_ = cury_ = rx_ = 0;
|
||||
curx_ = cury_ = 0;
|
||||
rowoffs_ = coloffs_ = 0;
|
||||
mark_set_ = false;
|
||||
mark_curx_ = mark_cury_ = 0;
|
||||
@@ -687,7 +683,7 @@ Buffer::OpenFromFile(const std::string &path, std::string &err)
|
||||
MarkContentChanged();
|
||||
|
||||
// Reset cursor/viewport state
|
||||
curx_ = cury_ = rx_ = 0;
|
||||
curx_ = cury_ = 0;
|
||||
rowoffs_ = coloffs_ = 0;
|
||||
mark_set_ = false;
|
||||
mark_curx_ = mark_cury_ = 0;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* or scratch document and manages:
|
||||
*
|
||||
* - Content storage: Uses PieceTable for efficient text operations
|
||||
* - Cursor state: Current position (curx_, cury_), rendered column (rx_)
|
||||
* - Cursor state: Current position (curx_, cury_)
|
||||
* - Viewport: Scroll offsets (rowoffs_, coloffs_) for display
|
||||
* - File backing: Optional association with a file on disk
|
||||
* - Undo/Redo: Integrated UndoSystem for operation history
|
||||
@@ -17,7 +17,6 @@
|
||||
*
|
||||
* 1. Cursor coordinates:
|
||||
* - (curx_, cury_): Logical character position in the document
|
||||
* - rx_: Rendered column accounting for tab expansion
|
||||
*
|
||||
* 2. File backing:
|
||||
* - Buffers can be file-backed (associated with a path) or scratch (unnamed)
|
||||
@@ -119,12 +118,6 @@ public:
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] std::size_t Rx() const
|
||||
{
|
||||
return rx_;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] std::size_t Nrows() const
|
||||
{
|
||||
return content_LineCount_();
|
||||
@@ -394,12 +387,6 @@ public:
|
||||
}
|
||||
|
||||
|
||||
void SetRenderX(const std::size_t rx)
|
||||
{
|
||||
rx_ = rx;
|
||||
}
|
||||
|
||||
|
||||
void SetOffsets(const std::size_t row, const std::size_t col)
|
||||
{
|
||||
rowoffs_ = row;
|
||||
@@ -740,7 +727,6 @@ private:
|
||||
|
||||
// State mirroring original C struct (without undo_tree)
|
||||
std::size_t curx_ = 0, cury_ = 0; // cursor position in characters
|
||||
std::size_t rx_ = 0; // render x (tabs expanded)
|
||||
std::size_t nrows_ = 0; // number of rows
|
||||
std::size_t rowoffs_ = 0, coloffs_ = 0; // viewport offsets
|
||||
mutable std::vector<Line> rows_; // materialized cache of rows (without trailing newlines)
|
||||
|
||||
@@ -389,7 +389,6 @@ if (BUILD_TESTS)
|
||||
tests/test_swap_cleanup2.cc
|
||||
tests/test_swap_git_editor.cc
|
||||
tests/test_piece_table.cc
|
||||
tests/test_search.cc
|
||||
tests/test_search_replace_flow.cc
|
||||
tests/test_reflow_paragraph.cc
|
||||
tests/test_reflow_indented_bullets.cc
|
||||
@@ -418,7 +417,6 @@ if (BUILD_TESTS)
|
||||
SearchHighlight.cc
|
||||
KKeymap.cc
|
||||
SwapRecorder.h
|
||||
OptimizedSearch.cc
|
||||
UndoNode.cc
|
||||
UndoTree.cc
|
||||
UndoSystem.cc
|
||||
|
||||
@@ -265,7 +265,6 @@ ensure_cursor_visible(const Editor &ed, Buffer &buf)
|
||||
}
|
||||
|
||||
buf.SetOffsets(rowoffs, coloffs);
|
||||
buf.SetRenderX(rx);
|
||||
}
|
||||
|
||||
|
||||
@@ -3051,7 +3050,6 @@ cmd_show_help(CommandContext &ctx)
|
||||
b.SetDirty(false);
|
||||
b.SetCursor(0, 0);
|
||||
b.SetOffsets(0, 0);
|
||||
b.SetRenderX(0);
|
||||
};
|
||||
|
||||
if (help_index != static_cast<std::size_t>(-1)) {
|
||||
|
||||
@@ -728,15 +728,11 @@ void
|
||||
Editor::Reset()
|
||||
{
|
||||
rows_ = cols_ = 0;
|
||||
mode_ = 0;
|
||||
kill_ = 0;
|
||||
no_kill_ = 0;
|
||||
dirtyex_ = 0;
|
||||
msg_.clear();
|
||||
msgtm_ = 0;
|
||||
uarg_ = 0;
|
||||
ucount_ = 0;
|
||||
repeatable_ = false;
|
||||
quit_requested_ = false;
|
||||
quit_confirm_pending_ = false;
|
||||
// Reset close-confirm/save state
|
||||
|
||||
@@ -79,19 +79,6 @@ public:
|
||||
}
|
||||
|
||||
|
||||
// Mode and flags (mirroring legacy fields)
|
||||
void SetMode(int m)
|
||||
{
|
||||
mode_ = m;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] int Mode() const
|
||||
{
|
||||
return mode_;
|
||||
}
|
||||
|
||||
|
||||
void SetKillChain(bool on)
|
||||
{
|
||||
kill_ = on ? 1 : 0;
|
||||
@@ -104,18 +91,6 @@ public:
|
||||
}
|
||||
|
||||
|
||||
void SetNoKill(bool on)
|
||||
{
|
||||
no_kill_ = on ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] bool NoKill() const
|
||||
{
|
||||
return no_kill_ != 0;
|
||||
}
|
||||
|
||||
|
||||
// Kill ring API
|
||||
void KillRingClear()
|
||||
{
|
||||
@@ -164,18 +139,6 @@ public:
|
||||
}
|
||||
|
||||
|
||||
void SetDirtyEx(int d)
|
||||
{
|
||||
dirtyex_ = d;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] int DirtyEx() const
|
||||
{
|
||||
return dirtyex_;
|
||||
}
|
||||
|
||||
|
||||
void SetUniversalArg(int uarg, int ucount)
|
||||
{
|
||||
uarg_ = uarg;
|
||||
@@ -189,12 +152,6 @@ public:
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] int UCount() const
|
||||
{
|
||||
return ucount_;
|
||||
}
|
||||
|
||||
|
||||
// --- Universal argument control (C-u) ---
|
||||
// Begin or extend a universal argument (like ke's uarg_start)
|
||||
void UArgStart();
|
||||
@@ -209,19 +166,6 @@ public:
|
||||
// If no universal argument active, returns 1.
|
||||
int UArgGet();
|
||||
|
||||
// Repeatable command flag: input layer can mark the next command as repeatable
|
||||
void SetRepeatable(bool on)
|
||||
{
|
||||
repeatable_ = on;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] bool Repeatable() const
|
||||
{
|
||||
return repeatable_;
|
||||
}
|
||||
|
||||
|
||||
// Status message storage. Rendering is renderer-dependent; the editor
|
||||
// merely stores the current message and its timestamp.
|
||||
void SetStatus(const std::string &message);
|
||||
@@ -666,14 +610,10 @@ private:
|
||||
};
|
||||
|
||||
std::size_t rows_ = 0, cols_ = 0;
|
||||
int mode_ = 0;
|
||||
int kill_ = 0; // KILL CHAIN
|
||||
int no_kill_ = 0; // don't kill in delete_row
|
||||
int dirtyex_ = 0;
|
||||
std::string msg_;
|
||||
std::time_t msgtm_ = 0;
|
||||
int uarg_ = 0, ucount_ = 0; // C-u support
|
||||
bool repeatable_ = false; // whether the next command is repeatable
|
||||
|
||||
std::vector<Buffer> buffers_;
|
||||
std::vector<Buffer> *shared_buffers_ = nullptr; // if set, use this instead of buffers_
|
||||
|
||||
@@ -52,11 +52,6 @@ struct RetryPolicy {
|
||||
}
|
||||
|
||||
|
||||
// Conservative policy for non-critical operations: fewer attempts, slower retries
|
||||
static RetryPolicy Conservative()
|
||||
{
|
||||
return RetryPolicy{2, std::chrono::milliseconds(200), 2.5, std::chrono::milliseconds(10000)};
|
||||
}
|
||||
};
|
||||
|
||||
// Retry a function with exponential backoff for transient errors
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#include "OptimizedSearch.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
void
|
||||
OptimizedSearch::build_bad_char(const std::string &pattern)
|
||||
{
|
||||
if (pattern == last_pat_)
|
||||
return;
|
||||
last_pat_ = pattern;
|
||||
std::fill(bad_char_.begin(), bad_char_.end(), -1);
|
||||
for (std::size_t i = 0; i < pattern.size(); ++i) {
|
||||
bad_char_[static_cast<unsigned char>(pattern[i])] = static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::size_t
|
||||
OptimizedSearch::find_first(const std::string &text, const std::string &pattern, std::size_t start)
|
||||
{
|
||||
const std::size_t n = text.size();
|
||||
const std::size_t m = pattern.size();
|
||||
if (m == 0)
|
||||
return start <= n ? start : std::string::npos;
|
||||
if (m > n || start >= n)
|
||||
return std::string::npos;
|
||||
build_bad_char(pattern);
|
||||
std::size_t s = start;
|
||||
while (s <= n - m) {
|
||||
std::size_t j = m;
|
||||
while (j > 0 && pattern[j - 1] == text[s + j - 1]) {
|
||||
--j;
|
||||
}
|
||||
if (j == 0) {
|
||||
return s; // match found
|
||||
}
|
||||
unsigned char badc = static_cast<unsigned char>(text[s + j - 1]);
|
||||
int bcidx = bad_char_[badc];
|
||||
std::size_t shift = (j - 1 > static_cast<std::size_t>(bcidx))
|
||||
? (j - 1 - static_cast<std::size_t>(bcidx))
|
||||
: 1;
|
||||
s += shift;
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::size_t>
|
||||
OptimizedSearch::find_all(const std::string &text, const std::string &pattern, std::size_t start)
|
||||
{
|
||||
std::vector<std::size_t> res;
|
||||
const std::size_t n = text.size();
|
||||
const std::size_t m = pattern.size();
|
||||
if (m == 0)
|
||||
return res;
|
||||
if (m > n || start >= n)
|
||||
return res;
|
||||
build_bad_char(pattern);
|
||||
std::size_t s = start;
|
||||
while (s <= n - m) {
|
||||
std::size_t j = m;
|
||||
while (j > 0 && pattern[j - 1] == text[s + j - 1]) {
|
||||
--j;
|
||||
}
|
||||
if (j == 0) {
|
||||
res.push_back(s);
|
||||
s += m; // non-overlapping
|
||||
continue;
|
||||
}
|
||||
unsigned char badc = static_cast<unsigned char>(text[s + j - 1]);
|
||||
int bcidx = bad_char_[badc];
|
||||
std::size_t shift = (j - 1 > static_cast<std::size_t>(bcidx))
|
||||
? (j - 1 - static_cast<std::size_t>(bcidx))
|
||||
: 1;
|
||||
s += shift;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// OptimizedSearch.h - Boyer–Moore (bad character) based substring search
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class OptimizedSearch {
|
||||
public:
|
||||
OptimizedSearch() = default;
|
||||
|
||||
// Find first occurrence at or after start. Returns npos if not found.
|
||||
std::size_t find_first(const std::string &text, const std::string &pattern, std::size_t start = 0);
|
||||
|
||||
// Find all non-overlapping matches at or after start. Returns starting indices.
|
||||
std::vector<std::size_t> find_all(const std::string &text, const std::string &pattern, std::size_t start = 0);
|
||||
|
||||
private:
|
||||
std::array<int, 256> bad_char_{};
|
||||
std::string last_pat_;
|
||||
|
||||
void build_bad_char(const std::string &pattern);
|
||||
};
|
||||
+12
-36
@@ -10,13 +10,6 @@
|
||||
PieceTable::PieceTable() = default;
|
||||
|
||||
|
||||
PieceTable::PieceTable(const std::size_t initialCapacity)
|
||||
{
|
||||
add_.reserve(initialCapacity);
|
||||
materialized_.reserve(initialCapacity);
|
||||
}
|
||||
|
||||
|
||||
PieceTable::PieceTable(const std::size_t initialCapacity,
|
||||
const std::size_t piece_limit,
|
||||
const std::size_t small_piece_threshold,
|
||||
@@ -109,14 +102,6 @@ PieceTable::operator=(PieceTable &&other) noexcept
|
||||
PieceTable::~PieceTable() = default;
|
||||
|
||||
|
||||
void
|
||||
PieceTable::Reserve(const std::size_t newCapacity)
|
||||
{
|
||||
add_.reserve(newCapacity);
|
||||
materialized_.reserve(newCapacity);
|
||||
}
|
||||
|
||||
|
||||
// Setter to allow tuning consolidation heuristics
|
||||
void
|
||||
PieceTable::SetConsolidationParams(const std::size_t piece_limit,
|
||||
@@ -417,12 +402,10 @@ PieceTable::RebuildLineIndex() const
|
||||
const std::string &src = pc.src == Source::Original ? original_ : add_;
|
||||
const char *base = src.data() + static_cast<std::ptrdiff_t>(pc.start);
|
||||
|
||||
for (std::size_t j = 0; j < pc.len; ++j) {
|
||||
if (base[j] == '\n') {
|
||||
// next line starts after the newline
|
||||
line_index_.push_back(pos + j + 1);
|
||||
}
|
||||
}
|
||||
const char *end = base + pc.len;
|
||||
// The next line starts after each newline.
|
||||
for (const char *p = base; (p = static_cast<const char *>(std::memchr(p, '\n', end - p))) != nullptr; ++p)
|
||||
line_index_.push_back(pos + static_cast<std::size_t>(p - base) + 1);
|
||||
|
||||
pos += pc.len;
|
||||
}
|
||||
@@ -800,14 +783,10 @@ PieceTable::GetLine(std::size_t line_num) const
|
||||
auto [start, end] = GetLineRange(line_num);
|
||||
if (end < start)
|
||||
return std::string();
|
||||
// Trim trailing '\n'
|
||||
if (end > start) {
|
||||
// To check last char, we can get it via GetRange of len 1 at end-1 without materializing whole
|
||||
std::string last = GetRange(end - 1, 1);
|
||||
if (!last.empty() && last[0] == '\n') {
|
||||
end -= 1;
|
||||
}
|
||||
}
|
||||
// Every line but the last ends at a newline (each newline starts an
|
||||
// index entry); leave it out.
|
||||
if (end > start && line_num + 1 < line_index_.size())
|
||||
end -= 1;
|
||||
return GetRange(start, end - start);
|
||||
}
|
||||
|
||||
@@ -837,13 +816,10 @@ PieceTable::LineColToByteOffset(std::size_t row, std::size_t col) const
|
||||
return total_size_;
|
||||
std::size_t start = line_index_[row];
|
||||
std::size_t end = (row + 1 < line_index_.size()) ? line_index_[row + 1] : total_size_;
|
||||
// Clamp col to line length excluding trailing newline
|
||||
if (end > start) {
|
||||
std::string last = GetRange(end - 1, 1);
|
||||
if (!last.empty() && last[0] == '\n') {
|
||||
end -= 1;
|
||||
}
|
||||
}
|
||||
// Clamp col to the line's length without its newline (every line but
|
||||
// the last has one).
|
||||
if (end > start && row + 1 < line_index_.size())
|
||||
end -= 1;
|
||||
std::size_t target = start + std::min(col, end - start);
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ class PieceTable {
|
||||
public:
|
||||
PieceTable();
|
||||
|
||||
explicit PieceTable(std::size_t initialCapacity);
|
||||
|
||||
// Advanced constructor allowing configuration of consolidation heuristics
|
||||
PieceTable(std::size_t initialCapacity,
|
||||
std::size_t piece_limit,
|
||||
@@ -70,9 +68,6 @@ public:
|
||||
|
||||
~PieceTable();
|
||||
|
||||
// Public API mirrors GapBuffer
|
||||
void Reserve(std::size_t newCapacity);
|
||||
|
||||
void AppendChar(char c);
|
||||
|
||||
void Append(const char *s, std::size_t len);
|
||||
@@ -109,13 +104,6 @@ public:
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] std::size_t Capacity() const
|
||||
{
|
||||
// Capacity for piece table isn't directly meaningful; report materialized capacity
|
||||
return materialized_.capacity();
|
||||
}
|
||||
|
||||
|
||||
// ===== New buffer-wide API (Phase 1) =====
|
||||
// Byte-based editing operations
|
||||
void Insert(std::size_t byte_offset, const char *text, std::size_t len);
|
||||
|
||||
@@ -1202,47 +1202,6 @@ SwapManager::RecordDelete(Buffer &buf, int row, int col, std::size_t len)
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SwapManager::RecordSplit(Buffer &buf, int row, int col)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(mtx_);
|
||||
auto it = journals_.find(&buf);
|
||||
if (it == journals_.end() || it->second.suspended)
|
||||
return;
|
||||
}
|
||||
Pending p;
|
||||
p.buf = &buf;
|
||||
p.type = SwapRecType::SPLIT;
|
||||
// payload v1: [encver u8=1][row u32][col u32]
|
||||
p.payload.push_back(1);
|
||||
put_le32(p.payload, static_cast<std::uint32_t>(std::max(0, row)));
|
||||
put_le32(p.payload, static_cast<std::uint32_t>(std::max(0, col)));
|
||||
enqueue(std::move(p));
|
||||
maybe_request_checkpoint(buf, 1);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SwapManager::RecordJoin(Buffer &buf, int row)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(mtx_);
|
||||
auto it = journals_.find(&buf);
|
||||
if (it == journals_.end() || it->second.suspended)
|
||||
return;
|
||||
}
|
||||
Pending p;
|
||||
p.buf = &buf;
|
||||
p.type = SwapRecType::JOIN;
|
||||
// payload v1: [encver u8=1][row u32]
|
||||
p.payload.push_back(1);
|
||||
put_le32(p.payload, static_cast<std::uint32_t>(std::max(0, row)));
|
||||
enqueue(std::move(p));
|
||||
maybe_request_checkpoint(buf, 1);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SwapManager::maybe_request_checkpoint(Buffer &buf, const std::size_t approx_edit_bytes)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace kte {
|
||||
enum class SwapRecType : std::uint8_t {
|
||||
INS = 1,
|
||||
DEL = 2,
|
||||
// SPLIT and JOIN are no longer written; replay still reads them.
|
||||
SPLIT = 3,
|
||||
JOIN = 4,
|
||||
META = 0xF0,
|
||||
@@ -213,10 +214,6 @@ private:
|
||||
|
||||
void RecordDelete(Buffer &buf, int row, int col, std::size_t len);
|
||||
|
||||
void RecordSplit(Buffer &buf, int row, int col);
|
||||
|
||||
void RecordJoin(Buffer &buf, int row);
|
||||
|
||||
void RecordCheckpoint(Buffer &buf, bool urgent_flush);
|
||||
|
||||
void maybe_request_checkpoint(Buffer &buf, std::size_t approx_edit_bytes);
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "UndoNode.h"
|
||||
|
||||
// Pool allocator for UndoNode to eliminate frequent malloc/free.
|
||||
// Uses fixed-size blocks to keep node addresses stable.
|
||||
class UndoNodePool {
|
||||
public:
|
||||
explicit UndoNodePool(std::size_t block_size = 64)
|
||||
: block_size_(block_size) {}
|
||||
|
||||
|
||||
UndoNode *acquire()
|
||||
{
|
||||
if (available_.empty())
|
||||
allocate_block();
|
||||
auto *node = available_.top();
|
||||
available_.pop();
|
||||
// Node comes zeroed; ensure links are reset
|
||||
node->text.clear();
|
||||
node->parent = nullptr;
|
||||
node->child = nullptr;
|
||||
node->next = nullptr;
|
||||
node->row = node->col = 0;
|
||||
node->type = UndoType{};
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
void release(UndoNode *node)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
// Clear heavy fields to free memory held by strings
|
||||
node->text.clear();
|
||||
node->parent = nullptr;
|
||||
node->child = nullptr;
|
||||
node->next = nullptr;
|
||||
node->row = node->col = 0;
|
||||
node->type = UndoType{};
|
||||
available_.push(node);
|
||||
}
|
||||
|
||||
private:
|
||||
void allocate_block()
|
||||
{
|
||||
// allocate a new block; keep ownership so memory stays valid
|
||||
std::unique_ptr<UndoNode[]> block(new UndoNode[block_size_]);
|
||||
UndoNode *base = block.get();
|
||||
blocks_.push_back(std::move(block));
|
||||
for (std::size_t i = 0; i < block_size_; ++i) {
|
||||
// ensure the node is reset; rely on default constructor/zero init
|
||||
available_.push(&base[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::size_t block_size_;
|
||||
std::vector<std::unique_ptr<UndoNode[]> > blocks_;
|
||||
std::stack<UndoNode *> available_;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "Test.h"
|
||||
#include "OptimizedSearch.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
static std::vector<std::size_t>
|
||||
ref_find_all(const std::string &text, const std::string &pat)
|
||||
{
|
||||
std::vector<std::size_t> res;
|
||||
if (pat.empty())
|
||||
return res;
|
||||
std::size_t from = 0;
|
||||
while (true) {
|
||||
auto p = text.find(pat, from);
|
||||
if (p == std::string::npos)
|
||||
break;
|
||||
res.push_back(p);
|
||||
from = p + pat.size();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
TEST(OptimizedSearch_basic_cases)
|
||||
{
|
||||
OptimizedSearch os;
|
||||
struct Case {
|
||||
std::string text;
|
||||
std::string pat;
|
||||
} cases[] = {
|
||||
{"", ""},
|
||||
{"", "a"},
|
||||
{"a", ""},
|
||||
{"a", "a"},
|
||||
{"aaaaa", "aa"},
|
||||
{"hello world", "world"},
|
||||
{"abcabcabc", "abc"},
|
||||
{"the quick brown fox", "fox"},
|
||||
};
|
||||
for (auto &c: cases) {
|
||||
auto got = os.find_all(c.text, c.pat, 0);
|
||||
auto ref = ref_find_all(c.text, c.pat);
|
||||
ASSERT_EQ(got, ref);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user