Files
whetstone_DSL/editor/src/editor_utils/EditorUtilsPart3.h
Bill 1696b92bb8 Add Sprints 46-59: governance, porting foundation, and language graduation prep (Steps 689-828)
Sprints 46-58 implement the cross-language porting foundation: language-to-IR
adapters, equivalence checking, gate validation, legacy ingestion, managed/dynamic
families, low-level/logic-actor semantics, debug workflow tooling, AST-native
family tools, Rust/CPP raising tools, system-level orchestration, query family,
and porting gates. Sprint 59 adds the governance layer (policy packs, review
boards, waiver packets, ambiguity triage, decision ledger) with the
whetstone_review_porting_decision MCP tool.

Also includes: sprint plans 46-130, MCP taskitem pipeline scripts,
CLAUDE.md, docs, and full test matrix (steps 689-828).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 13:18:10 -07:00

145 lines
5.8 KiB
C++

// ---------------------------------------------------------------------------
// Syntax highlight color map
// ---------------------------------------------------------------------------
static ImVec4 tokenColor(TokenCategory cat) {
switch (cat) {
case TokenCategory::Keyword: return ImVec4(0.77f, 0.49f, 0.86f, 1.0f); // purple
case TokenCategory::String: return ImVec4(0.81f, 0.54f, 0.37f, 1.0f); // orange
case TokenCategory::Comment: return ImVec4(0.42f, 0.52f, 0.35f, 1.0f); // green
case TokenCategory::Number: return ImVec4(0.71f, 0.80f, 0.55f, 1.0f); // light green
case TokenCategory::Function: return ImVec4(0.86f, 0.86f, 0.55f, 1.0f); // yellow
case TokenCategory::Parameter: return ImVec4(0.60f, 0.78f, 0.90f, 1.0f); // light blue
case TokenCategory::Type: return ImVec4(0.30f, 0.70f, 0.68f, 1.0f); // teal
case TokenCategory::Operator: return ImVec4(0.86f, 0.86f, 0.86f, 1.0f); // white
case TokenCategory::Punctuation: return ImVec4(0.60f, 0.60f, 0.60f, 1.0f); // gray
case TokenCategory::Builtin: return ImVec4(0.30f, 0.70f, 0.90f, 1.0f); // blue
case TokenCategory::Identifier: return ImVec4(0.60f, 0.78f, 0.90f, 1.0f); // light blue
default: return ImVec4(0.86f, 0.86f, 0.86f, 1.0f); // white
}
}
// ---------------------------------------------------------------------------
// Render syntax-highlighted text (read-only preview)
// ---------------------------------------------------------------------------
static void RenderHighlightedText(const std::string& text,
const std::vector<HighlightSpan>& spans) {
if (text.empty()) return;
// Build a color for each byte position
// Default = plain text color
std::vector<TokenCategory> charCats(text.size(), TokenCategory::Plain);
for (const auto& span : spans) {
for (uint32_t i = span.start; i < span.end && i < charCats.size(); ++i) {
charCats[i] = span.category;
}
}
// Render line by line, span by span
size_t pos = 0;
int lineNum = 1;
while (pos < text.size()) {
// Find end of line
size_t eol = text.find('\n', pos);
if (eol == std::string::npos) eol = text.size();
// Line number
ImGui::TextColored(ImVec4(0.45f, 0.45f, 0.45f, 1.0f), "%4d ", lineNum);
ImGui::SameLine(0.0f, 0.0f);
// Render spans within this line
size_t linePos = pos;
while (linePos < eol) {
// Find the extent of the current color
TokenCategory cat = charCats[linePos];
size_t spanEnd = linePos + 1;
while (spanEnd < eol && charCats[spanEnd] == cat) ++spanEnd;
std::string chunk = text.substr(linePos, spanEnd - linePos);
ImGui::TextColored(tokenColor(cat), "%s", chunk.c_str());
if (spanEnd < eol) ImGui::SameLine(0.0f, 0.0f);
linePos = spanEnd;
}
if (linePos == pos) {
// Empty line
ImGui::TextUnformatted("");
}
pos = eol + 1;
++lineNum;
}
}
// ---------------------------------------------------------------------------
// File tree rendering
// ---------------------------------------------------------------------------
static void RenderFileTree(const FileNode& node, EditorState& state) {
if (!node.isDir) {
if (ImGui::Selectable(node.name.c_str())) {
state.doOpen(node.path, state.defaultBufferMode());
}
return;
}
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
if (!state.workspaceRoot.empty() && node.path == state.workspaceRoot) {
flags |= ImGuiTreeNodeFlags_DefaultOpen;
}
if (ImGui::TreeNodeEx(node.name.c_str(), flags)) {
for (const auto& child : node.children) {
RenderFileTree(child, state);
}
ImGui::TreePop();
}
}
// ---------------------------------------------------------------------------
// Welcome screen rendering
// ---------------------------------------------------------------------------
static void RenderWelcome(WelcomeScreen& welcome, EditorState& state, std::string& lastDialogPath) {
ImGui::Text("%s", welcome.getTitle().c_str());
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", welcome.getSubtitle().c_str());
ImGui::Separator();
ImGui::Text("Quick Actions");
if (ImGui::Button("New File")) {
std::string lang = state.active() ? state.active()->language : "python";
state.createBuffer(state.makeUntitledName(), "", lang, state.defaultBufferMode());
}
ImGui::SameLine();
if (ImGui::Button("Open File")) {
auto path = FileDialog::openFile({"Open File", {"*.py","*.cpp","*.h","*.el","*.js","*.ts","*.java","*.rs","*.go","*.org","*.md","*.txt","*.docx","*.*"}, lastDialogPath});
if (!path.empty()) {
lastDialogPath = path;
state.doOpen(path, state.defaultBufferMode());
}
}
ImGui::SameLine();
if (ImGui::Button("Open Folder")) {
auto path = FileDialog::openFolder({"Open Folder", state.workspaceRoot});
if (!path.empty()) {
std::string error;
if (state.setWorkspaceRoot(path, &error)) {
lastDialogPath = path;
} else {
state.notify(NotificationLevel::Error,
"Failed to open folder: " + error);
}
}
}
ImGui::Separator();
ImGui::Text("Recent Files");
for (const auto& rf : welcome.getRecentFiles()) {
if (ImGui::Selectable(rf.displayName.c_str())) {
state.doOpen(rf.path, bufferModeFromString(rf.mode));
}
}
ImGui::Separator();
ImGui::Text("Tip");
ImGui::TextWrapped("%s", welcome.getTip(0).c_str());
}