Step 555: add breakpoint system with edit reconciliation

This commit is contained in:
Bill
2026-02-17 10:28:30 -07:00
parent 78a72aaf5f
commit a3cfbb8f58
4 changed files with 311 additions and 0 deletions

View File

@@ -3874,4 +3874,13 @@ target_link_libraries(step554_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step555_test tests/step555_test.cpp)
target_include_directories(step555_test PRIVATE src)
target_link_libraries(step555_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -0,0 +1,118 @@
#pragma once
// Step 555: Breakpoint System
#include <map>
#include <set>
#include <string>
#include <vector>
struct Breakpoint {
std::string id;
std::string filePath;
int line = 0;
bool enabled = true;
std::string condition;
int hitCountTarget = 0;
int currentHits = 0;
};
class BreakpointSystem {
public:
bool addLineBreakpoint(const std::string& id,
const std::string& filePath,
int line) {
return addBreakpoint({id, filePath, line, true, "", 0, 0});
}
bool addConditionalBreakpoint(const std::string& id,
const std::string& filePath,
int line,
const std::string& condition) {
return addBreakpoint({id, filePath, line, true, condition, 0, 0});
}
bool addHitCountBreakpoint(const std::string& id,
const std::string& filePath,
int line,
int hitCountTarget) {
if (hitCountTarget <= 0) return false;
return addBreakpoint({id, filePath, line, true, "", hitCountTarget, 0});
}
bool enable(const std::string& id) {
auto* bp = findMutable(id);
if (!bp) return false;
bp->enabled = true;
return true;
}
bool disable(const std::string& id) {
auto* bp = findMutable(id);
if (!bp) return false;
bp->enabled = false;
return true;
}
bool remove(const std::string& id) {
auto it = index_.find(id);
if (it == index_.end()) return false;
breakpoints_.erase(breakpoints_.begin() + static_cast<long>(it->second));
index_.erase(it);
rebuildIndex();
return true;
}
bool shouldBreak(const std::string& id, bool conditionEvaluatedTrue) {
auto* bp = findMutable(id);
if (!bp || !bp->enabled) return false;
if (!bp->condition.empty() && !conditionEvaluatedTrue) return false;
if (bp->hitCountTarget > 0) {
++bp->currentHits;
return bp->currentHits >= bp->hitCountTarget;
}
return true;
}
void reconcileOnFileEdit(const std::string& filePath,
int fromLine,
int lineDelta) {
for (auto& bp : breakpoints_) {
if (bp.filePath != filePath) continue;
if (bp.line >= fromLine) {
bp.line += lineDelta;
if (bp.line < 1) bp.line = 1;
}
}
rebuildIndex();
}
std::vector<Breakpoint> list() const { return breakpoints_; }
private:
std::vector<Breakpoint> breakpoints_;
std::map<std::string, size_t> index_;
bool addBreakpoint(const Breakpoint& bp) {
if (bp.id.empty() || bp.filePath.empty() || bp.line <= 0) return false;
if (index_.count(bp.id) != 0) return false;
breakpoints_.push_back(bp);
index_[bp.id] = breakpoints_.size() - 1;
return true;
}
Breakpoint* findMutable(const std::string& id) {
auto it = index_.find(id);
if (it == index_.end()) return nullptr;
return &breakpoints_[it->second];
}
void rebuildIndex() {
index_.clear();
for (size_t i = 0; i < breakpoints_.size(); ++i) {
index_[breakpoints_[i].id] = i;
}
}
};

View File

@@ -0,0 +1,150 @@
// Step 555: Breakpoint System (12 tests)
#include "BreakpointSystem.h"
#include <iostream>
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
static const Breakpoint* findBp(const std::vector<Breakpoint>& list, const std::string& id) {
for (const auto& bp : list) if (bp.id == id) return &bp;
return nullptr;
}
void test_add_line_breakpoint() {
TEST(add_line_breakpoint);
BreakpointSystem b;
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "line breakpoint should add");
CHECK(b.list().size() == 1, "one breakpoint expected");
PASS();
}
void test_add_conditional_breakpoint() {
TEST(add_conditional_breakpoint);
BreakpointSystem b;
CHECK(b.addConditionalBreakpoint("bp1", "a.cpp", 12, "x > 0"), "conditional breakpoint should add");
auto bp = findBp(b.list(), "bp1");
CHECK(bp && bp->condition == "x > 0", "condition should persist");
PASS();
}
void test_add_hit_count_breakpoint() {
TEST(add_hit_count_breakpoint);
BreakpointSystem b;
CHECK(b.addHitCountBreakpoint("bp1", "a.cpp", 5, 3), "hit-count breakpoint should add");
auto bp = findBp(b.list(), "bp1");
CHECK(bp && bp->hitCountTarget == 3, "hit target should persist");
PASS();
}
void test_add_invalid_hit_count_breakpoint_fails() {
TEST(add_invalid_hit_count_breakpoint_fails);
BreakpointSystem b;
CHECK(!b.addHitCountBreakpoint("bp1", "a.cpp", 5, 0), "zero hit count should fail");
PASS();
}
void test_enable_disable_persistence() {
TEST(enable_disable_persistence);
BreakpointSystem b;
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
CHECK(b.disable("bp1"), "disable should work");
auto bp = findBp(b.list(), "bp1");
CHECK(bp && !bp->enabled, "breakpoint should be disabled");
CHECK(b.enable("bp1"), "enable should work");
bp = findBp(b.list(), "bp1");
CHECK(bp && bp->enabled, "breakpoint should be re-enabled");
PASS();
}
void test_should_break_simple_enabled_breakpoint() {
TEST(should_break_simple_enabled_breakpoint);
BreakpointSystem b;
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
CHECK(b.shouldBreak("bp1", true), "enabled line breakpoint should break");
PASS();
}
void test_should_not_break_when_disabled() {
TEST(should_not_break_when_disabled);
BreakpointSystem b;
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
CHECK(b.disable("bp1"), "disable should work");
CHECK(!b.shouldBreak("bp1", true), "disabled breakpoint should not break");
PASS();
}
void test_conditional_breakpoint_requires_true_condition() {
TEST(conditional_breakpoint_requires_true_condition);
BreakpointSystem b;
CHECK(b.addConditionalBreakpoint("bp1", "a.cpp", 10, "x > 0"), "add should work");
CHECK(!b.shouldBreak("bp1", false), "false condition should not break");
CHECK(b.shouldBreak("bp1", true), "true condition should break");
PASS();
}
void test_hit_count_breakpoint_triggers_at_threshold() {
TEST(hit_count_breakpoint_triggers_at_threshold);
BreakpointSystem b;
CHECK(b.addHitCountBreakpoint("bp1", "a.cpp", 10, 3), "add should work");
CHECK(!b.shouldBreak("bp1", true), "hit1 should not break");
CHECK(!b.shouldBreak("bp1", true), "hit2 should not break");
CHECK(b.shouldBreak("bp1", true), "hit3 should break");
PASS();
}
void test_reconcile_on_file_edit_shifts_lines() {
TEST(reconcile_on_file_edit_shifts_lines);
BreakpointSystem b;
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add bp1");
CHECK(b.addLineBreakpoint("bp2", "a.cpp", 20), "add bp2");
b.reconcileOnFileEdit("a.cpp", 15, 3);
auto bp1 = findBp(b.list(), "bp1");
auto bp2 = findBp(b.list(), "bp2");
CHECK(bp1 && bp1->line == 10, "bp1 should remain before edit line");
CHECK(bp2 && bp2->line == 23, "bp2 should shift by +3");
PASS();
}
void test_reconcile_clamps_line_to_one() {
TEST(reconcile_clamps_line_to_one);
BreakpointSystem b;
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 2), "add should work");
b.reconcileOnFileEdit("a.cpp", 1, -10);
auto bp = findBp(b.list(), "bp1");
CHECK(bp && bp->line == 1, "line should clamp to 1");
PASS();
}
void test_remove_breakpoint() {
TEST(remove_breakpoint);
BreakpointSystem b;
CHECK(b.addLineBreakpoint("bp1", "a.cpp", 10), "add should work");
CHECK(b.remove("bp1"), "remove should work");
CHECK(b.list().empty(), "list should be empty after remove");
PASS();
}
int main() {
std::cout << "Step 555: Breakpoint System\n";
test_add_line_breakpoint(); // 1
test_add_conditional_breakpoint(); // 2
test_add_hit_count_breakpoint(); // 3
test_add_invalid_hit_count_breakpoint_fails(); // 4
test_enable_disable_persistence(); // 5
test_should_break_simple_enabled_breakpoint(); // 6
test_should_not_break_when_disabled(); // 7
test_conditional_breakpoint_requires_true_condition(); // 8
test_hit_count_breakpoint_triggers_at_threshold(); // 9
test_reconcile_on_file_edit_shifts_lines(); // 10
test_reconcile_clamps_line_to_one(); // 11
test_remove_breakpoint(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -9802,3 +9802,37 @@ resume/stop transitions and multi-target per-buffer context support.
- `editor/src/DebugSessionModel.h` within header-size limit (`106` <= `600`)
- `editor/tests/step554_test.cpp` within test-file size guidance (`140` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 555: Breakpoint System
**Status:** PASS (12/12 tests)
Implements breakpoint management for line, conditional, and hit-count
breakpoints, including enable/disable persistence and line reconciliation across
file edits.
**Files added:**
- `editor/src/BreakpointSystem.h` - breakpoint subsystem:
- line, conditional, and hit-count breakpoint creation
- enable/disable/remove operations
- runtime break-decision evaluation (condition + hit-count semantics)
- file-edit reconciliation with line-shift/clamp behavior
- `editor/tests/step555_test.cpp` - 12 tests covering:
- breakpoint creation variants and invalid input guardrails
- enable/disable persistence behavior
- simple/conditional/hit-count break evaluation behavior
- file-edit reconciliation behavior
- removal behavior
**Files modified:**
- `editor/CMakeLists.txt` - `step555_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step555_test step554_test` - PASS
- `./editor/build-native/step555_test` - PASS (12/12)
- `./editor/build-native/step554_test` - PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/BreakpointSystem.h` within header-size limit (`118` <= `600`)
- `editor/tests/step555_test.cpp` within test-file size guidance (`150` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`