Phase 3c: Classical editing mode (Steps 50-53)

Implement TextASTSync and TextEditor — the core text editing layer
that bridges raw text and the structured AST. TextASTSync provides
bidirectional text-AST sync with debounce via TreeSitterParser and
generators. TextEditor wraps a text buffer with edit operations,
undo/redo that tracks AST state, find/replace, and selection.

New files: TextASTSync.h, TextEditor.h, step50_test.cpp
Updated: step51/53 tests (replaced stubs with real includes),
CMakeLists.txt (tree-sitter linking), PROGRESS.md
Fixed: step53 find-position off-by-one, canRedo assert logic

19/19 tests pass (step50: 8, step51: 5, step53: 6)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-02-08 15:45:59 -07:00
parent 41166812b0
commit 742c33d813
7 changed files with 380 additions and 86 deletions

69
editor/src/TextASTSync.h Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
// Step 51: Text-AST bidirectional synchronization
//
// TextASTSync bridges raw text editing and the structured AST:
// setText() → stores text, marks parse pending (debounce)
// syncNow() → parses text via TreeSitterParser → updates AST
// getText() → regenerates text from current AST via generator
// getAST() → returns current Module*
// setAST() → replaces AST directly (structured edit path)
#include "ast/Parser.h"
#include "ast/Generator.h"
#include <string>
#include <memory>
class TextASTSync {
public:
void setText(const std::string& text, const std::string& language) {
text_ = text;
language_ = language;
parsePending_ = true;
}
Module* getAST() const {
return module_.get();
}
void setAST(std::unique_ptr<Module> module) {
module_ = std::move(module);
parsePending_ = false;
}
std::string getText() const {
if (!module_) return text_;
if (language_ == "python") {
PythonGenerator gen;
return gen.generate(module_.get());
} else if (language_ == "cpp") {
CppGenerator gen;
return gen.generate(module_.get());
} else if (language_ == "elisp") {
ElispGenerator gen;
return gen.generate(module_.get());
}
return text_;
}
void syncNow() {
if (!parsePending_) return;
if (language_ == "python") {
module_ = TreeSitterParser::parsePython(text_);
} else if (language_ == "cpp") {
module_ = TreeSitterParser::parseCpp(text_);
} else if (language_ == "elisp") {
module_ = TreeSitterParser::parseElisp(text_);
}
parsePending_ = false;
}
bool isParsePending() const { return parsePending_; }
const std::string& getLanguage() const { return language_; }
private:
std::string text_;
std::string language_;
std::unique_ptr<Module> module_;
bool parsePending_ = false;
};