diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index c316bc9..6280893 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -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) diff --git a/editor/src/BreakpointSystem.h b/editor/src/BreakpointSystem.h new file mode 100644 index 0000000..7bb4bdb --- /dev/null +++ b/editor/src/BreakpointSystem.h @@ -0,0 +1,118 @@ +#pragma once +// Step 555: Breakpoint System + +#include +#include +#include +#include + +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(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 list() const { return breakpoints_; } + +private: + std::vector breakpoints_; + std::map 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; + } + } +}; diff --git a/editor/tests/step555_test.cpp b/editor/tests/step555_test.cpp new file mode 100644 index 0000000..5901711 --- /dev/null +++ b/editor/tests/step555_test.cpp @@ -0,0 +1,150 @@ +// Step 555: Breakpoint System (12 tests) + +#include "BreakpointSystem.h" + +#include + +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& 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; +} diff --git a/progress.md b/progress.md index 9204406..b665ea8 100644 --- a/progress.md +++ b/progress.md @@ -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`