Step 355: Shortcuts Panel Integration (8/8 tests)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
85
editor/src/KeyboardShortcutsPanel.h
Normal file
85
editor/src/KeyboardShortcutsPanel.h
Normal 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_;
|
||||
};
|
||||
161
editor/tests/step355_test.cpp
Normal file
161
editor/tests/step355_test.cpp
Normal 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;
|
||||
}
|
||||
36
progress.md
36
progress.md
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user