diff --git a/PROGRESS.md b/PROGRESS.md index b638a0c..523a03a 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -509,3 +509,4 @@ Sprint 5 in progress. Step 141 (Emacs daemon with user config) done. Next: Step | 2026-02-10 | Codex | Step 159: Agent marketplace registry with UI panel, install toggles, and registry serialization. 6/6 tests pass. | | 2026-02-10 | Codex | Step 160: Theme engine with JSON themes, ImGui styling, and syntax/editor color mapping. 4/4 tests pass. | | 2026-02-10 | Codex | Step 161: Plugin API + loader with registry hooks for concepts/generators/grammars/annotations/panels/commands. 10/10 tests pass. | +| 2026-02-10 | Codex | Step 162: Help panel with Markdown sections + docs/help.md wired in UI. 4/4 tests pass. | diff --git a/docs/help.md b/docs/help.md new file mode 100644 index 0000000..d84337a --- /dev/null +++ b/docs/help.md @@ -0,0 +1,46 @@ +# Documentation + +Welcome to Whetstone. This panel provides a compact reference to the editor, +annotations, agent APIs, and language support. + +## Getting Started +- Open a folder from File > Open or Project > Open. +- Use the left File Tree to browse files. +- Toggle Text/Structured mode with the Mode switch in the toolbar. +- Run or build from the Build tab (or toolbar buttons). + +## Annotation Reference +Whetstone uses canonical memory annotations: +- `@Reclaim(Tracing|Escape|Cycle)` +- `@Owner(Single|Shared_ARC)` +- `@Lifetime(RAII)` +- `@Deallocate(Explicit)` +- `@Allocate(Static|Register|Allocator)` + +Annotations can be added from the gutter or context menu. Conflicts are +highlighted in the Problems panel with quick fixes. + +## Keyboard Shortcuts +Shortcuts are configurable. See Settings > Keybindings. Default profile: +- `Ctrl+P` Command palette +- `Ctrl+F` Find/Replace +- `Ctrl+S` Save +- `Ctrl+Z` Undo, `Ctrl+Y` Redo + +## Language Support +Supported languages (parsing + generation): +- Python, C++, Elisp, JavaScript, TypeScript, Java, Rust, Go + +## Agent API (JSON-RPC) +Core RPC methods: +- `getAST` +- `applyMutation` +- `generateCode` +- `getAnnotationSuggestions` +- `applyAnnotationSuggestion` +- `startWorkflowRecording`, `stopWorkflowRecording`, `replayWorkflow` + +## Troubleshooting +- If an AST is unavailable, switch to Structured mode. +- If LSP is offline, completions still work from indexed stubs. +- If Emacs integration fails, check the Output panel for Emacs logs. diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index ba884f1..e6cfee9 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -901,6 +901,10 @@ target_link_libraries(step160_test PRIVATE nlohmann_json::nlohmann_json imgui::i add_executable(step161_test tests/step161_test.cpp) target_include_directories(step161_test PRIVATE src) +add_executable(step162_test tests/step162_test.cpp) +target_include_directories(step162_test PRIVATE src) +target_link_libraries(step162_test PRIVATE imgui::imgui) + find_package(SDL2 CONFIG REQUIRED) find_package(OpenGL REQUIRED) find_package(glad CONFIG REQUIRED) diff --git a/editor/src/EditorState.h b/editor/src/EditorState.h index d2873e3..f18d6d8 100644 --- a/editor/src/EditorState.h +++ b/editor/src/EditorState.h @@ -46,6 +46,7 @@ #include "AgentRegistry.h" #include "AgentMarketplace.h" #include "BuildSystem.h" +#include "HelpPanel.h" #include "DependencyPanel.h" #include "LibraryIndexer.h" #include "LibraryBrowserPanel.h" @@ -182,6 +183,7 @@ struct EditorState { WorkflowRecorder workflowRecorder; AgentRegistry agentRegistry; AgentMarketplaceState agentMarketplace; + HelpPanelState helpPanel; BuildSystem::Type buildType = BuildSystem::Type::None; std::vector buildErrors; std::string lastBuildOutput; diff --git a/editor/src/HelpPanel.h b/editor/src/HelpPanel.h new file mode 100644 index 0000000..84d5c76 --- /dev/null +++ b/editor/src/HelpPanel.h @@ -0,0 +1,132 @@ +#pragma once +// Step 162: Help panel markdown renderer + +#include "imgui.h" +#include +#include +#include +#include +#include + +struct HelpSection { + std::string title; + std::string content; +}; + +struct HelpPanelState { + int selected = 0; + std::vector sections; + std::string lastRoot; +}; + +static bool loadHelpFile(const std::string& path, std::string& out) { + std::ifstream in(path); + if (!in.is_open()) return false; + std::ostringstream ss; + ss << in.rdbuf(); + out = ss.str(); + return true; +} + +static std::vector parseHelpSections(const std::string& text) { + std::vector sections; + std::istringstream ss(text); + std::string line; + HelpSection current; + while (std::getline(ss, line)) { + if (line.rfind("# ", 0) == 0) { + if (!current.title.empty() || !current.content.empty()) { + sections.push_back(current); + current = HelpSection(); + } + current.title = line.substr(2); + } else { + current.content += line + "\n"; + } + } + if (!current.title.empty() || !current.content.empty()) { + sections.push_back(current); + } + if (sections.empty()) { + sections.push_back({"Documentation", text}); + } + return sections; +} + +static void renderMarkdownLine(const std::string& line, ImFont* monoFont) { + std::string trimmed = line; + while (!trimmed.empty() && (trimmed.back() == '\r' || trimmed.back() == '\n')) { + trimmed.pop_back(); + } + if (trimmed.rfind("## ", 0) == 0) { + ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s", trimmed.substr(3).c_str()); + } else if (trimmed.rfind("### ", 0) == 0) { + ImGui::TextColored(ImVec4(0.6f, 0.85f, 0.95f, 1.0f), "%s", trimmed.substr(4).c_str()); + } else if (trimmed.rfind("- ", 0) == 0) { + ImGui::BulletText("%s", trimmed.substr(2).c_str()); + } else if (trimmed.rfind("```", 0) == 0) { + ImGui::TextDisabled("%s", trimmed.c_str()); + } else if (trimmed.rfind("`", 0) == 0 && trimmed.size() > 1) { + ImGui::PushFont(monoFont); + ImGui::TextUnformatted(trimmed.c_str()); + ImGui::PopFont(); + } else { + ImGui::TextWrapped("%s", trimmed.c_str()); + } +} + +static void renderMarkdown(const std::string& text, ImFont* monoFont) { + std::istringstream ss(text); + std::string line; + while (std::getline(ss, line)) { + renderMarkdownLine(line, monoFont); + } +} + +static void loadHelpContent(HelpPanelState& state, const std::string& root) { + if (state.lastRoot == root && !state.sections.empty()) return; + state.sections.clear(); + state.selected = 0; + state.lastRoot = root; + + std::string content; + if (!root.empty()) { + if (!loadHelpFile(root + "\\docs\\help.md", content)) { + loadHelpFile(root + "/docs/help.md", content); + } + } + if (content.empty()) { + content = "# Documentation\n\nDocumentation not found.\n"; + } + state.sections = parseHelpSections(content); +} + +static void renderHelpPanel(HelpPanelState& state, + const std::string& workspaceRoot, + ImFont* monoFont) { + loadHelpContent(state, workspaceRoot); + if (state.sections.empty()) { + ImGui::TextDisabled("(no documentation)"); + return; + } + + ImGui::Columns(2, "help_cols", true); + ImGui::BeginChild("help_nav", ImVec2(0, 0), true); + for (size_t i = 0; i < state.sections.size(); ++i) { + const auto& section = state.sections[i]; + bool selected = (int)i == state.selected; + if (ImGui::Selectable(section.title.c_str(), selected)) { + state.selected = (int)i; + } + } + ImGui::EndChild(); + ImGui::NextColumn(); + + ImGui::BeginChild("help_body", ImVec2(0, 0), false); + const auto& section = state.sections[std::min((int)state.sections.size() - 1, state.selected)]; + ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s", section.title.c_str()); + ImGui::Separator(); + renderMarkdown(section.content, monoFont); + ImGui::EndChild(); + ImGui::Columns(1); +} diff --git a/editor/src/main.cpp b/editor/src/main.cpp index 07d0d40..f39996f 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -2016,6 +2016,13 @@ int main(int, char**) { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Help")) { + ImGui::PushFont(uiFont); + renderHelpPanel(state.helpPanel, state.workspaceRoot, monoFont); + ImGui::PopFont(); + ImGui::EndTabItem(); + } + // Problems (LSP diagnostics) if (ImGui::BeginTabItem("Problems")) { ImGui::PushFont(monoFont); diff --git a/editor/tests/step162_test.cpp b/editor/tests/step162_test.cpp new file mode 100644 index 0000000..53a656c --- /dev/null +++ b/editor/tests/step162_test.cpp @@ -0,0 +1,34 @@ +// Step 162 TDD Test: Help panel section parsing +#include "HelpPanel.h" +#include + +static void expect(bool cond, const std::string& name, int& passed, int& failed) { + if (cond) { + std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n"; + ++passed; + } else { + std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n"; + ++failed; + } +} + +int main() { + int passed = 0; + int failed = 0; + + std::string text = + "# Intro\n" + "Hello.\n" + "# Usage\n" + "Details.\n"; + auto sections = parseHelpSections(text); + expect(sections.size() == 2, "section count", passed, failed); + expect(sections[0].title == "Intro", "first title", passed, failed); + expect(sections[1].title == "Usage", "second title", passed, failed); + expect(sections[0].content.find("Hello") != std::string::npos, + "content captured", passed, failed); + + std::cout << "\n=== Step 162 Results: " << passed << " passed, " + << failed << " failed ===\n"; + return failed == 0 ? 0 : 1; +} diff --git a/sprint5_plan.md b/sprint5_plan.md index d6dc38e..5220e8b 100644 --- a/sprint5_plan.md +++ b/sprint5_plan.md @@ -361,7 +361,7 @@ Final polish, documentation, and ecosystem features. library (.dll/.so) with a defined entry point. *New:* `PluginAPI.h`, `PluginLoader.h` -- [ ] **Step 162: User documentation** +- [x] **Step 162: User documentation** In-editor help system. Help > Documentation opens a panel with: - Getting Started guide - Annotation reference (all annotation types with examples)