2 Commits

Author SHA1 Message Date
d2d155f211 Fix data race.
+ Add thread-safety with mutexes in `PieceTable` and `Buffer`
+ Bump version to 1.5.9
2026-01-28 01:03:58 -08:00
8634eb78f0 Refactor Init method across all frontends to include argc and argv for improved argument handling consistency. 2026-01-12 10:35:24 -08:00
15 changed files with 137 additions and 100 deletions

View File

@@ -412,6 +412,7 @@ Buffer::GetLineView(std::size_t row) const
void
Buffer::ensure_rows_cache() const
{
std::lock_guard<std::mutex> lock(buffer_mutex_);
if (!rows_cache_dirty_)
return;
rows_.clear();

View File

@@ -14,6 +14,7 @@
#include <cstdint>
#include "syntax/HighlighterEngine.h"
#include "Highlight.h"
#include <mutex>
// Forward declaration for swap journal integration
namespace kte {
@@ -482,4 +483,6 @@ private:
std::unique_ptr<kte::HighlighterEngine> highlighter_;
// Non-owning pointer to swap recorder managed by Editor/SwapManager
kte::SwapRecorder *swap_rec_ = nullptr;
mutable std::mutex buffer_mutex_;
};

View File

@@ -4,7 +4,7 @@ project(kte)
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 20)
set(KTE_VERSION "1.5.8")
set(KTE_VERSION "1.5.9")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.

View File

@@ -12,7 +12,7 @@ public:
virtual ~Frontend() = default;
// Initialize the frontend (create window/terminal, etc.)
virtual bool Init(Editor &ed) = 0;
virtual bool Init(int &argc, char **argv, Editor &ed) = 0;
// Execute one iteration (poll input, dispatch, draw). Set running=false to exit.
virtual void Step(Editor &ed, bool &running) = 0;

View File

@@ -30,8 +30,10 @@
static auto kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
bool
GUIFrontend::Init(Editor &ed)
GUIFrontend::Init(int &argc, char **argv, Editor &ed)
{
(void) argc;
(void) argv;
// Attach editor to input handler for editor-owned features (e.g., universal argument)
input_.Attach(&ed);
// editor dimensions will be initialized during the first Step() frame

View File

@@ -17,7 +17,7 @@ public:
~GUIFrontend() override = default;
bool Init(Editor &ed) override;
bool Init(int &argc, char **argv, Editor &ed) override;
void Step(Editor &ed, bool &running) override;

View File

@@ -273,6 +273,7 @@ PieceTable::addPieceFront(Source src, std::size_t start, std::size_t len)
void
PieceTable::materialize() const
{
std::lock_guard<std::mutex> lock(mutex_);
if (!dirty_) {
return;
}
@@ -348,6 +349,7 @@ PieceTable::coalesceNeighbors(std::size_t index)
void
PieceTable::InvalidateLineIndex() const
{
std::lock_guard<std::mutex> lock(mutex_);
line_index_dirty_ = true;
}
@@ -355,22 +357,29 @@ PieceTable::InvalidateLineIndex() const
void
PieceTable::RebuildLineIndex() const
{
if (!line_index_dirty_)
std::lock_guard<std::mutex> lock(mutex_);
if (!line_index_dirty_) {
return;
}
line_index_.clear();
line_index_.push_back(0);
std::size_t pos = 0;
for (const auto &pc: pieces_) {
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);
}
}
pos += pc.len;
}
line_index_dirty_ = false;
}
@@ -692,14 +701,18 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const
len = total_size_ - byte_offset;
// Fast path: return cached value if version/offset/len match
{
std::lock_guard<std::mutex> lock(mutex_);
if (range_cache_.valid && range_cache_.version == version_ &&
range_cache_.off == byte_offset && range_cache_.len == len) {
return range_cache_.data;
}
}
std::string out;
out.reserve(len);
if (!dirty_) {
std::lock_guard<std::mutex> lock(mutex_);
// Already materialized; slice directly
out.assign(materialized_.data() + static_cast<std::ptrdiff_t>(byte_offset), len);
} else {
@@ -723,11 +736,14 @@ PieceTable::GetRange(std::size_t byte_offset, std::size_t len) const
}
// Update cache
{
std::lock_guard<std::mutex> lock(mutex_);
range_cache_.valid = true;
range_cache_.version = version_;
range_cache_.off = byte_offset;
range_cache_.len = len;
range_cache_.data = out;
}
return out;
}
@@ -739,15 +755,21 @@ PieceTable::Find(const std::string &needle, std::size_t start) const
return start <= total_size_ ? start : std::numeric_limits<std::size_t>::max();
if (start > total_size_)
return std::numeric_limits<std::size_t>::max();
{
std::lock_guard<std::mutex> lock(mutex_);
if (find_cache_.valid &&
find_cache_.version == version_ &&
find_cache_.needle == needle &&
find_cache_.start == start) {
return find_cache_.result;
}
}
materialize();
auto pos = materialized_.find(needle, start);
std::size_t pos;
{
std::lock_guard<std::mutex> lock(mutex_);
pos = materialized_.find(needle, start);
if (pos == std::string::npos)
pos = std::numeric_limits<std::size_t>::max();
// Update cache
@@ -756,6 +778,7 @@ PieceTable::Find(const std::string &needle, std::size_t start) const
find_cache_.needle = needle;
find_cache_.start = start;
find_cache_.result = pos;
}
return pos;
}
@@ -764,6 +787,9 @@ void
PieceTable::WriteToStream(std::ostream &out) const
{
// Stream the content piece-by-piece without forcing full materialization
// No lock needed for original_ and add_ if they are not being modified.
// Since this is a const method and kte's piece table isn't modified by multiple threads
// (only queried), we just iterate pieces_.
for (const auto &p: pieces_) {
if (p.len == 0)
continue;

View File

@@ -8,6 +8,7 @@
#include <ostream>
#include <vector>
#include <limits>
#include <mutex>
class PieceTable {
@@ -181,4 +182,6 @@ private:
mutable RangeCache range_cache_;
mutable FindCache find_cache_;
mutable std::mutex mutex_;
};

View File

@@ -658,10 +658,8 @@ private:
} // namespace
bool
GUIFrontend::Init(Editor &ed)
GUIFrontend::Init(int &argc, char **argv, Editor &ed)
{
int argc = 0;
char **argv = nullptr;
app_ = new QApplication(argc, argv);
window_ = new MainWindow(input_);

View File

@@ -18,7 +18,7 @@ public:
~GUIFrontend() override = default;
bool Init(Editor &ed) override;
bool Init(int &argc, char **argv, Editor &ed) override;
void Step(Editor &ed, bool &running) override;

View File

@@ -8,8 +8,10 @@
bool
TerminalFrontend::Init(Editor &ed)
TerminalFrontend::Init(int &argc, char **argv, Editor &ed)
{
(void) argc;
(void) argv;
// Ensure Control keys reach the app: disable XON/XOFF and dsusp/susp bindings (e.g., ^S/^Q, ^Y on macOS)
{
struct termios tio{};

View File

@@ -21,7 +21,7 @@ public:
// Adjust if your terminal needs a different threshold.
static constexpr int kEscDelayMs = 50;
bool Init(Editor &ed) override;
bool Init(int &argc, char **argv, Editor &ed) override;
void Step(Editor &ed, bool &running) override;

View File

@@ -4,8 +4,10 @@
bool
TestFrontend::Init(Editor &ed)
TestFrontend::Init(int &argc, char **argv, Editor &ed)
{
(void) argc;
(void) argv;
ed.SetDimensions(24, 80);
return true;
}

View File

@@ -13,7 +13,7 @@ public:
~TestFrontend() override = default;
bool Init(Editor &ed) override;
bool Init(int &argc, char **argv, Editor &ed) override;
void Step(Editor &ed, bool &running) override;

View File

@@ -112,7 +112,7 @@ RunStressHighlighter(unsigned seconds)
int
main(int argc, const char *argv[])
main(int argc, char *argv[])
{
std::setlocale(LC_ALL, "");
@@ -136,7 +136,7 @@ main(int argc, const char *argv[])
int opt;
int long_index = 0;
unsigned stress_seconds = 0;
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "gthV", long_opts, &long_index)) != -1) {
while ((opt = getopt_long(argc, argv, "gthV", long_opts, &long_index)) != -1) {
switch (opt) {
case 'g':
req_gui = true;
@@ -303,7 +303,7 @@ main(int argc, const char *argv[])
}
#endif
if (!fe->Init(editor)) {
if (!fe->Init(argc, argv, editor)) {
std::cerr << "kte: failed to initialize frontend" << std::endl;
return 1;
}