Step 355: Shortcuts Panel Integration (8/8 tests)

This commit is contained in:
Bill
2026-02-16 09:09:37 -07:00
parent b089fa0cd0
commit 1470417c60
6 changed files with 294 additions and 2 deletions

View File

@@ -2114,4 +2114,8 @@ add_executable(step354_test tests/step354_test.cpp)
target_include_directories(step354_test PRIVATE src)
target_link_libraries(step354_test PRIVATE nlohmann_json::nlohmann_json)
add_executable(step355_test tests/step355_test.cpp)
target_include_directories(step355_test PRIVATE src)
target_link_libraries(step355_test PRIVATE nlohmann_json::nlohmann_json)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -12,6 +12,7 @@ struct CommandEntry {
std::string id;
std::string label;
std::string shortcut;
std::string symbols;
std::string icon;
std::string category;
std::string inputHint;
@@ -64,7 +65,8 @@ public:
const std::string& inputHint = "",
bool inlineInput = false,
const std::vector<std::string>& aliases = {},
const std::string& icon = "") {
const std::string& icon = "",
const std::string& symbols = "") {
auto& entry = commands_[id];
entry.id = id;
entry.label = label;
@@ -75,6 +77,7 @@ public:
entry.inlineInput = inlineInput;
entry.aliases = aliases;
entry.icon = icon;
entry.symbols = symbols;
}
void registerFromKeybindings(const KeybindingRegistry& registry) {
@@ -89,7 +92,8 @@ public:
"",
false,
{action.id},
WhetstoneIcons::Search
WhetstoneIcons::Search,
binding.toSymbolsAuto()
);
}
}

View File

@@ -205,6 +205,7 @@ public:
// View actions
registerAction("command-palette", "Command Palette", "View");
registerAction("keyboard-shortcuts", "Keyboard Shortcuts", "Help");
registerAction("toggle-file-tree", "Toggle File Tree", "View");
registerAction("toggle-ast-view", "Toggle AST View", "View");
registerAction("toggle-diagnostics", "Toggle Diagnostics", "View");
@@ -234,6 +235,7 @@ public:
bind("find-in-file", KeyCombo::ctrl("F"));
bind("find-in-project", KeyCombo::ctrlShift("F"));
bind("command-palette", KeyCombo::ctrl("P"));
bind("keyboard-shortcuts", KeyCombo::ctrlShift("?"));
bind("toggle-file-tree", KeyCombo::ctrl("B"));
bind("toggle-diagnostics", KeyCombo::ctrlShift("D"));
bind("run-pipeline", KeyCombo::fkey("F5"));

View File

@@ -0,0 +1,85 @@
#pragma once
// Step 355: Keyboard shortcuts panel model.
// Headless grouping/filtering/customize behavior for shortcut display.
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include "KeybindingRegistry.h"
struct ShortcutRow {
std::string actionId;
std::string label;
std::string category;
std::string symbols;
std::string description;
};
class KeyboardShortcutsPanel {
public:
void open() { visible_ = true; }
void close() { visible_ = false; }
bool isVisible() const { return visible_; }
void setFilter(const std::string& text) { filter_ = text; }
const std::string& filter() const { return filter_; }
void loadFromRegistry(const KeybindingRegistry& registry, bool macOS = false) {
rows_.clear();
for (const auto& action : registry.getActions()) {
ShortcutRow row;
row.actionId = action.id;
row.label = action.label;
row.category = action.category;
row.description = action.description;
row.symbols = registry.getSymbols(action.id, macOS);
rows_.push_back(row);
}
std::sort(rows_.begin(), rows_.end(), [](const ShortcutRow& a, const ShortcutRow& b) {
if (a.category != b.category) return a.category < b.category;
return a.label < b.label;
});
}
const std::vector<ShortcutRow>& rows() const { return rows_; }
std::vector<ShortcutRow> filteredRows() const {
if (filter_.empty()) return rows_;
std::string needle = toLower(filter_);
std::vector<ShortcutRow> out;
for (const auto& row : rows_) {
std::string hay = toLower(row.label + " " + row.category + " " + row.actionId);
if (hay.find(needle) != std::string::npos) out.push_back(row);
}
return out;
}
std::map<std::string, std::vector<ShortcutRow>> groupedRows() const {
std::map<std::string, std::vector<ShortcutRow>> groups;
for (const auto& row : filteredRows()) {
groups[row.category].push_back(row);
}
return groups;
}
void requestCustomize() { customizeRequested_ = true; }
bool consumeCustomizeRequest() {
bool value = customizeRequested_;
customizeRequested_ = false;
return value;
}
private:
static std::string toLower(const std::string& s) {
std::string out = s;
std::transform(out.begin(), out.end(), out.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return out;
}
bool visible_ = false;
bool customizeRequested_ = false;
std::string filter_;
std::vector<ShortcutRow> rows_;
};

View File

@@ -0,0 +1,161 @@
// Step 355: Phase 13c Integration — Keyboard Shortcuts Help Panel (8 tests)
#include <cassert>
#include <iostream>
#include <string>
#include "KeybindingRegistry.h"
#include "CommandPalette.h"
#include "MenuBar.h"
#include "KeyboardShortcutsPanel.h"
int main() {
int passed = 0;
// Test 1: all default bindings displayed in shortcuts panel
{
KeybindingRegistry reg;
reg.loadDefaults();
KeyboardShortcutsPanel panel;
panel.loadFromRegistry(reg);
assert(!panel.rows().empty());
assert(panel.rows().size() >= static_cast<size_t>(reg.bindingCount()));
std::cout << "Test 1 PASSED: default bindings displayed\n";
passed++;
}
// Test 2: Ctrl+S triggers save action
{
KeybindingRegistry reg;
reg.loadDefaults();
KeyCombo::InputState in;
in.ctrl = true;
in.key = "s";
auto action = reg.processInput(in);
assert(action.has_value());
assert(action.value() == "save-buffer");
std::cout << "Test 2 PASSED: Ctrl+S triggers save\n";
passed++;
}
// Test 3: Ctrl+P opens command palette
{
KeybindingRegistry reg;
reg.loadDefaults();
CommandPalette palette;
assert(!palette.isOpen());
KeyCombo::InputState in;
in.ctrl = true;
in.key = "P";
auto action = reg.processInput(in);
if (action.has_value() && action.value() == "command-palette") {
palette.open();
}
assert(palette.isOpen());
std::cout << "Test 3 PASSED: Ctrl+P opens palette\n";
passed++;
}
// Test 4: command palette shows key symbols for registered actions
{
KeybindingRegistry reg;
reg.loadDefaults();
CommandPalette palette;
palette.registerFromKeybindings(reg);
auto all = palette.all();
assert(!all.empty());
int symbolCount = 0;
for (const auto& c : all) {
if (!c.shortcut.empty()) {
assert(!c.symbols.empty());
symbolCount++;
}
}
assert(symbolCount >= 10);
std::cout << "Test 4 PASSED: palette shows key symbols\n";
passed++;
}
// Test 5: menu items show correct key symbols
{
KeybindingRegistry reg;
reg.loadDefaults();
auto menu = MenuBar::buildDefaults(reg);
auto save = menu.findItem("save-buffer");
auto undo = menu.findItem("undo");
assert(!save.symbols.empty());
assert(!undo.symbols.empty());
std::cout << "Test 5 PASSED: menu symbols correct\n";
passed++;
}
// Test 6: rebind action updates shortcuts panel
{
KeybindingRegistry reg;
reg.loadDefaults();
KeyboardShortcutsPanel panel;
panel.loadFromRegistry(reg);
std::string before;
for (const auto& row : panel.rows()) {
if (row.actionId == "save-buffer") before = row.symbols;
}
reg.bind("save-buffer", KeyCombo::ctrl("W"));
panel.loadFromRegistry(reg);
std::string after;
for (const auto& row : panel.rows()) {
if (row.actionId == "save-buffer") after = row.symbols;
}
assert(before != after);
assert(after.find("W") != std::string::npos);
std::cout << "Test 6 PASSED: rebind updates panel\n";
passed++;
}
// Test 7: key symbols render on current platform mode
{
KeybindingRegistry reg;
reg.loadDefaults();
KeyboardShortcutsPanel panel;
panel.loadFromRegistry(reg);
bool hasAnySymbol = false;
for (const auto& row : panel.rows()) {
if (!row.symbols.empty()) {
hasAnySymbol = true;
break;
}
}
assert(hasAnySymbol);
std::cout << "Test 7 PASSED: platform symbols render\n";
passed++;
}
// Test 8: full keyboard workflow Ctrl+P -> "pipeline" -> Enter -> run-pipeline
{
KeybindingRegistry reg;
reg.loadDefaults();
CommandPalette palette;
palette.registerFromKeybindings(reg);
bool pipelineRan = false;
KeyCombo::InputState openIn;
openIn.ctrl = true;
openIn.key = "p";
auto openAction = reg.processInput(openIn);
assert(openAction.has_value());
assert(openAction.value() == "command-palette");
palette.open();
assert(palette.isOpen());
auto matches = palette.search("pipeline");
assert(!matches.empty());
auto selected = palette.executeSelected(matches);
assert(selected.has_value());
if (selected.value() == "run-pipeline") pipelineRan = true;
assert(pipelineRan);
std::cout << "Test 8 PASSED: keyboard workflow executes pipeline\n";
passed++;
}
std::cout << "\nResults: " << passed << "/8\n";
assert(passed == 8);
return 0;
}

View File

@@ -1827,6 +1827,42 @@ symbol display path is consistent with step 352.
- `step353_test` — PASS (12/12) regression coverage
- `step354_test` — PASS (12/12) new step coverage
### Step 355: Phase 13c Integration — Keyboard Shortcuts Help Panel
**Status:** PASS (8/8 tests)
Integrated a dedicated keyboard-shortcuts panel model and connected the end-to-end
keyboard flow across the keybinding registry, command palette, and menu bar.
This provides grouped/filterable shortcut rows plus refresh-on-rebind behavior and
a full keyboard-driven command execution path.
**Files created:**
- `editor/src/KeyboardShortcutsPanel.h` — panel data model:
- visibility state, filter text, grouped rows by category
- row projection from `KeybindingRegistry` (label/category/symbols)
- customize-request signal (`requestCustomize` / `consumeCustomizeRequest`)
- `editor/tests/step355_test.cpp` — 8 integration tests:
1. defaults appear in shortcuts panel
2. Ctrl+S resolves to save action
3. Ctrl+P opens command palette
4. command palette entries include key symbols
5. menu entries include key symbols
6. rebind propagates to shortcuts panel
7. symbol rendering works for current platform mode
8. keyboard workflow: Ctrl+P → query pipeline → execute run-pipeline
**Files modified:**
- `editor/src/KeybindingRegistry.h` — added `keyboard-shortcuts` action and default
binding (`Ctrl+Shift+?`) to support help-panel keyboard access
- `editor/src/CommandPalette.h` — added `symbols` field on command entries and
populated symbol strings from keybindings
- `editor/CMakeLists.txt``step355_test` target
**Verification run:**
- `step351_test` — PASS (12/12) regression coverage
- `step353_test` — PASS (12/12) regression coverage
- `step354_test` — PASS (12/12) regression coverage
- `step355_test` — PASS (8/8) new integration coverage
# Roadmap Planning — Sprints 12-25+
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)